Hi,
We have an API provider that exposes its APIs using mTLS + OAuth2.0 client credential grant. The API request flow would be -
1. Consumer APP invokes Apigee proxy
2. Apigee proxy validates the consuming app and if valid proceeds to the next step, otherwise returns an error.
3. Apigee checks if it has a valid token (to connect to the backend) in the cache. If yes, proceed to step 6, otherwise proceed to next step.
4. Apigee invokes the backends OAuth provider to obtain an OAuth token. The OAuth providers Token endpoint is protected using mTLS, so Apigee needs to connect to the same using mTLS
5. Apigee obtains the OAuth token from the backend OAuth provider. Apigee caches this token based on the expiry obtained in the Token API response
6. Apigee injects the OAuth token in the request and invokes the functional endpoint exposed by the backend provider. This functional endpoint is protected using mTLS + OAuth2.0, so along with the injected Oauth token, Apigee has to perform a mTLS handshake with the backend
7. Apigee obtains the response from the functional endpoint of the backend
8. Apigee returns the response as-is to the consumer application
Amongst all the steps listed above, I would like to understand if step 4 & step 6 can be achieved using Apigee.
If yes, please do advise the recommended solution to achieve this.
Any solutions would be greatly appreciated.
Regards,
Ian
Yes, you can do that.
To construct that kind of a proxy in Apigee, you would rely on the builtin policies like
Pretty straightforward. Using 2-way TLS for the upstream is a VERY common pattern. Using OAuth2 bearer tokens for the upstream is a common pattern. Combining them in the way you describe, is less common in my experience. But in any case what you describe is pretty straightforward.
Thank you for the response and the explanation. It is helpful.
Just adding up little flow & can work with you if you face any issues but it is mostly easy to implement..
1. Proxy Pre-Flow:
Step 0 (optional - follow your existing security policies flow)
- Shared flow - you may want to enable basic security guard rails (enable few regex,rate limiting etc) with in the shared flow
https://cloud.google.com/apigee/docs/api-platform/reference/policies/reference-overview-policy
Step 1 - Shared flow which does verify of access token (assuming you have oauth enabled) & throw error if it is invalid
Sample verification
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<OAuthV2 async="false" continueOnError="false" enabled="true" name="OA-VerifyAccessToken">
<DisplayName>OA-VerifyAccessToken</DisplayName>
<Properties/>
<Attributes/>
<ExternalAuthorization>false</ExternalAuthorization>
<Operation>VerifyAccessToken</Operation>
<SupportedGrantTypes/>
<GenerateResponse enabled="true"/>
<Tokens/>
</OAuthV2>
Step -2 - Remove the header which you don't want to propagate further using Assign Message as it is not required..
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage async="false" continueOnError="false" enabled="true" name="AM-RemoveHeaders">
<DisplayName>AM-RemoveHeaders</DisplayName>
<Properties/>
<Remove>
<Headers>
<Header name="Authorization"/>
</Headers>
</Remove>
<IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
<AssignTo createNew="false" transport="http" type="request"/>
</AssignMessage>
Step 3 - You can have standard conditional flow to forward the req (eg: GET/POST what ever is the functionality)
Step 4 - Now add the backend related calls/caching mechanism with in PostFlow ( the proxy endpoint PostFlow executes before the target endpoint PreFlow). It looks simple,clean and no duplicate on different conditional flow..
https://cloud.google.com/apigee/docs/api-platform/cache/persistence-tools
in Apigee X, the named caches get implicitly created at runtime when you reference one within a cache policy..
Proxy PostFlow:
First do the lookup in cache for a token
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<LookupCache async="false" continueOnError="false" enabled="true" name="LC-Sample">
<DisplayName>LC-Sample</DisplayName>
<CacheResource>Sample</CacheResource>
<AssignTo>access_token</AssignTo>
<Scope>Exclusive</Scope>
<CacheKey>
<KeyFragment>access_token</KeyFragment>
</CacheKey>
</LookupCache>
Next perform service callout on below condition (make sure you are reading the variables from kvm in pre-flow itself after step 0 - didn't add it and assuming you know how to read the encrypted kvm values) .
Also make sure you setup the required setup for ssl -
<Condition>lookupcache.LC-Sample.cachehit = false</Condition>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ServiceCallout async="false" continueOnError="false" enabled="true" name="SC-Sample">
<DisplayName>SC-Sample</DisplayName>
<Properties/>
<Request clearPayload="true" variable="sampleRequest">
<Set>
<FormParams>
<FormParam name="grant_type">{private.sample_grant_type}</FormParam>
<FormParam name="client_id">{private.sample_client_id}</FormParam>
<FormParam name="client_secret">{private.sample_client_secret}</FormParam>
</FormParams>
<Headers>
<Header name="Content-Type">application/x-www-form-urlencoded</Header>
</Headers>
<Verb>POST</Verb>
</Set>
<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
</Request>
<Response>sampleResponse</Response>
<HTTPTargetConnection>
<Properties/>
<URL>https://{private.sample_tokenUrl}</URL>
<SSLInfo>
<Enabled>true</Enabled>
<ClientAuthEnabled>true</ClientAuthEnabled>
<KeyStore>{var-for-keystore}</KeyStore>
<KeyAlias>{var-for-keyalias}</KeyAlias>
<TrustStore>{var-for-truststore}</TrustStore>
<IgnoreValidationErrors>false</IgnoreValidationErrors>
</SSLInfo>
</HTTPTargetConnection>
</ServiceCallout>
Now extract the response from the service callout using below condition
<Condition>lookupcache.LC-Sample.cachehit = false and sampleResponse.content != null</Condition>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<ExtractVariables async="false" continueOnError="false" enabled="true" name="EV-Sample">
<DisplayName>EV-Sample</DisplayName>
<Properties/>
<JSONPayload>
<Variable name="access_token">
<JSONPath>$.access_token</JSONPath>
</Variable>
</JSONPayload>
<Source clearPayload="false">sampleResponse.content</Source>
</ExtractVariables>
Now you populate the token using below condition
<Condition>lookupcache.LC-Sample.cachehit = false</Condition>
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<PopulateCache async="false" continueOnError="false" enabled="true" name="PC-Sample">
<DisplayName>PC-Sample</DisplayName>
<CacheResource>Sample</CacheResource>
<Source>access_token</Source>
<Scope>Exclusive</Scope>
<CacheKey>
<KeyFragment>access_token</KeyFragment>
</CacheKey>
<ExpirySettings>
<TimeoutInSeconds ref="private.sampleTimeOut">12345</TimeoutInSec>
</ExpirySettings>
</PopulateCache>
Step 5 - Final assign target url & access token in Target PreFlow
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<AssignMessage async="false" continueOnError="false" enabled="true" name="AM-SampleTarget">
<DisplayName>AM-SampleTarget</DisplayName>
<Properties/>
<AssignVariable>
<Name>target.url</Name>
<Template>https://{private.sample_targetUrl}</Template>
</AssignVariable>
<Set>
<Headers>
<Header name="Authorization">Bearer {access_token}</Header>
</Headers>
</Set>
<IgnoreUnresolvedVariables>false</IgnoreUnresolvedVariables>
<AssignTo createNew="false" transport="http" type="request"/>
</AssignMessage>
Error handling & logging follow your standard process..
Good Luck.
Thank you for the response and detailed explanation. I will give this a try and surely reach out in case of any further questions.