A shared flow is a collection of reusable policies and resources that can be consumed from multiple API proxies. It is like an API proxy but does not have endpoints. While the SAP Cloud Platform API Management does not yet support Shared Flow out-of-box, I will show in this blog on how to achieve it using a workaround.
We will see it with an example of abstracting OAuth Client Credentials implementation policies into a shared flow. The shared flow can be called from any API Proxy to fetch an Access token using OAuth Client Credentials grant from any Authorization server.
I will use SAP Cloud Platform OAuth Service as the Authorization server, however, this Shared flow can be used for any Authorization Server that supports Client Credential grant as specified in RFC 6749. We will use SAP CPI as the resource server, however, this approach can be used to access any resource in SAP Cloud Platform protected with SCP OAuth Service.
We have 2 Key Value maps involved here. The first one for saving shared secrets required between Main and Shared Proxy and the next one to save the Authorization Server(s) connection credentials like ID, Secret, and token URL.
Create an Encrypted Key Value Map named SharedSecret and maintain the value of your choice.
(Note: Check Encrypt Key Value Map checkbox)
Create an Encrypted Key Value map named OAuthCreds. This Key Value map is to maintain the connection credentials for any number of Authorization Server.
(Note: Check Encrypt Key Value Map checkbox)
I created a Key Value map like below for one Auth Server. These 3 entries to be repeated for other authorization servers.
Main API Proxy(s) will contain 4 Policies. These policies can be saved as a Policy template and applied to any API Proxy that requires connecting to an API using OAuth Client Credentials Grant.
You can download the policy template "PT_OAuthConnection" from Git Repository or Implement it from the API Policy below.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<LookupCache async="false" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
<CacheKey>
<Prefix>OAUTH</Prefix>
<KeyFragment>scpdev</KeyFragment>
</CacheKey>
<Scope>Global</Scope>
<AssignTo>sapapim.accessToken</AssignTo>
</LookupCache>
<KeyValueMapOperations mapIdentifier="SharedSecret" async="true" continueOnError="true" enabled="true" xmlns="http://www.sap.com/apimgmt">
<Get assignTo="private.shared.secret">
<Key>
<Parameter>secret</Parameter>
</Key>
</Get>
<Scope>environment</Scope>
</KeyValueMapOperations>
<ServiceCallout async="true" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
<Request>
<Set>
<Headers>
<Header name="tenant">scpdev</Header>
<Header name="secret">{private.shared.secret}</Header>
</Headers>
<Verb>POST</Verb>
</Set>
</Request>
<Response>sapapim.oauth.response</Response>
<Timeout>15000</Timeout>
<LocalTargetConnection>
<Path>/p318236trial/get/oauth</Path>
</LocalTargetConnection>
</ServiceCallout>
<AssignMessage async="false" continueOnError="false" enabled="true" xmlns='http://www.sap.com/apimgmt'>
<Remove>
<Headers/>
</Remove>
<Set>
<Headers>
<Header name="Authorization">Bearer {sapapim.accessToken}</Header>
</Headers>
</Set>
<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
<AssignTo createNew="false" type="request">request</AssignTo>
</AssignMessage>
This Shared Flow (API Proxy) in the workaround way will have a proxy endpoint however we will protect it with a shared secret that is technically accessible only from the runtime by API Proxy. I have designed the Shared Flow to have a target end-point for Authorization Server. [But it can also be done with a service call-out step and setting the Target Endpoint to None in route rule. So we can block this flow Proxy and Target Endpoint making it an absolute Shared Flow.]
<KeyValueMapOperations mapIdentifier="SharedSecret" async="true" continueOnError="true" enabled="true" xmlns="http://www.sap.com/apimgmt">
<Get assignTo="private.shared.secret">
<Key>
<Parameter>secret</Parameter>
</Key>
</Get>
<Scope>environment</Scope>
</KeyValueMapOperations>
<Javascript async="false" continueOnError="false" enabled="true" timeLimit="200" xmlns='http://www.sap.com/apimgmt'>
<ResourceURL>jsc://checkSharedSecret.js</ResourceURL>
</Javascript>
var envsecret = context.getVariable("private.shared.secret");
var reqsecret = context.getVariable("request.header.secret");
if (envsecret != reqsecret)
throw 'Incorrect Shared Secret';
<Javascript async="false" continueOnError="false" enabled="true" timeLimit="200" xmlns='http://www.sap.com/apimgmt'>
<ResourceURL>jsc://setVariables.js</ResourceURL>
</Javascript>
var tenant = context.getVariable("request.header.tenant");
context.setVariable("authserver.ClientID", tenant.concat("_ClientID"));
context.setVariable("authserver.ClientSecret", tenant.concat("_ClientSecret"));
context.setVariable("authserver.tURL", tenant.concat("_tURL"));
context.setVariable("req.tenant",tenant);
<KeyValueMapOperations mapIdentifier="OAuthCreds" async="true" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
<Get assignTo="private.authserver.clientID">
<Key>
<Parameter ref="authserver.ClientID"></Parameter>
</Key>
</Get>
<Get assignTo="private.authserver.clientSecret">
<Key>
<Parameter ref="authserver.ClientSecret"></Parameter>
</Key>
</Get>
<Get assignTo="private.authserver.tURL">
<Key>
<Parameter ref="authserver.tURL"></Parameter>
</Key>
</Get>
<Scope>environment</Scope>
</KeyValueMapOperations>
<BasicAuthentication async='true' continueOnError='false' enabled='true' xmlns='http://www.sap.com/apimgmt'>
<Operation>Encode</Operation>
<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
<User ref='private.authserver.clientID'></User>
<Password ref='private.authserver.clientSecret'></Password>
<AssignTo createNew="true">sapapim.auth</AssignTo>
</BasicAuthentication>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage async="false" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
<Remove>
<Headers>
</Headers>
</Remove>
<Set>
<Headers>
<Header name="Authorization">{sapapim.auth}</Header>
<Header name="Content-Type">application/x-www-form-urlencoded</Header>
</Headers>
<FormParams>
<FormParam name="grant_type">client_credentials</FormParam>
</FormParams>
</Set>
<AssignVariable>
<Name>target.url</Name>
<Ref>private.authserver.tURL</Ref>
</AssignVariable>
<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
<AssignTo createNew="false" type="request">request</AssignTo>
</AssignMessage>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ExtractVariables async="false" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
<JSONPayload>
<Variable name="sapapim.accessToken" type="string">
<JSONPath>$.access_token</JSONPath>
</Variable>
<Variable name="sapapim.expiresIn" type="integer">
<JSONPath>$.expires_in</JSONPath>
</Variable>
</JSONPayload>
<Source>message.content</Source>
</ExtractVariables>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<PopulateCache async="false" continueOnError="false" enabled="true" xmlns="http://www.sap.com/apimgmt">
<CacheKey>
<Prefix>OAUTH</Prefix>
<KeyFragment ref="req.tenant"></KeyFragment>
</CacheKey>
<Scope>Global</Scope>
<ExpirySettings>
<TimeoutInSec ref="sapapim.expiresIn"></TimeoutInSec>
</ExpirySettings>
<Source>sapapim.accessToken</Source>
</PopulateCache>
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
12 | |
9 | |
9 | |
7 | |
7 | |
5 | |
5 | |
5 | |
4 | |
4 |