OAuth custom flow

Requirements:

Plantronics provides SaaS offering for analytics related to device/ calls etc. We recently released our first Cloud API developer flow here:

http://developer.plantronics.com/oda/apis

PM Pro is a multi tenant SaaS webapi which provides some of our data to its customers. Our APIs allow 3rd party integrators to pull this data. we are exploring how to implement OAuth token security for the APIs we provide. Our PM Pro tenant owner owns the data. There is no specific user owner. Currently we use API key and a secret key called tenantApiCode which is available to the PM pro tenant admin which can be shared with the 3rd party app which integrated and pull the data. The 3rd party apps are only allowed to consume data from APIs is the appId is registered and authorize by the PM Pro admin(we have our own internal UI flow for the admin to authorize an app).

An api provide might try and pull data for a specific tenant like this API:

http://developer.plantronics.com/oda/apis/get/reports/assets/tenant/%7BtenantApiCode%7D/devicedistribution

The tenantApiCode above is a secret which identifies the tenant that the API will server the response for.

Now for oauth requirement:

1. An API consumer (App registered in developer portal) should be issued a token only if : the appId(apiKey) is a registered in APIgee dev portal.

2.And, if the tenantApiCode and appId (apigee secret key) combination is authorized(This state sits in our Cloud can be accessed from an API) to server data it is trying to pull is authorized by the tenant admin.

Solved Solved
1 10 890
1 ACCEPTED SOLUTION

Congrats on the release of the ODA APIs!

Requirements:

1. An API consumer (App registered in developer portal) should be issued a token only if : the appId(apiKey) is a registered in APIgee dev portal.

2.And, if the tenantApiCode and appId (apigee secret key) combination is authorized(This state sits in our Cloud can be accessed from an API) to server data it is trying to pull is authorized by the tenant admin.

OK, regarding the first requirement, that is how OAuth tokens work. You didn't say anything about authorizing individual users at the 3rd party integrators. So I'll assume that you're using a client-credentials flow. In that case, the 3rd party integrator will pass the client_id + client_secret (aka consumer_id + consumer_secret) to the /token endpoint at Apigee Edge. Apigee Edge then examines the credentials and if

  • the credentials are known
  • the creds are not revoked
  • the creds are not expired
  • the owning app is not marked "revoked"
  • the owning developer is not marked "inactive"

..then Apigee Edge will issue a token . This is all done by the magic policy called OAuthV2/GenerateAccessToken .

Regarding the tenantApiCode.... what you can do is, prior to requesting the generation of the access token, you can call the authorization API,which queries the tenants authorized per API Key. Then, attach the list of tenants as a custom attribute to the token.

On subsequent requests bearing that token, verification of the token will implicitly retrieve the list of tenants authorized for that token.

You can then enforce a check on the tenant-in-the-request against the list of tenants authorized for the appid. I think JavaScript would be the easiest way to do this.

This is really straightforward, and is a really common approach.


For token issuance, the OAuthV2/GeneratorAccessToken looks like this:

<OAuthV2 name='OAuthV2-GenerateAccessToken-CC'>
  <ExpiresIn>1800000</ExpiresIn>
  <RefreshTokenExpiresIn>691200000</RefreshTokenExpiresIn>
  <SupportedGrantTypes>
    <GrantType>client_credentials</GrantType>
  </SupportedGrantTypes>
  <GrantType>request.formparam.grant_type</GrantType>
  <Attributes>
    <Attribute name="tenant_list" ref="tenant_list_from_api_service" display="true"/>
  </Attributes>
  <GenerateResponse enabled='true'/>
</OAuthV2>

This policy assumes that the context variable "tenant_list" contains the list of tenants for this app (client id).

OK that's how you issue tokens. In subsequent arriving at the API Proxy you will want to verify tokens. Do that with OAuthV2/VerifyAccessToken. That step implicitly retrieves the list of tenants authorized for that token, and stores it into a context variable. Assuming you have the GenerateAccessToken policy configured as above, the variable name is accesstoken.tenant_list. Subsequently, you will want to check the tenant in the request against the tenant list in the token. So, you can use ExtractVariables to extract the tenantid from the request URI. Like this:

<ExtractVariables name="ExtractVariables-1">
   <DisplayName>Extract a portion of the url path</DisplayName>
   <Source>request</Source>
   <URIPath>
      <Pattern ignoreCase="true">/oda/apis/get/reports/assets/tenant/{tenantCode}/devicedistribution</Pattern>
   </URIPath>
   <VariablePrefix>urirequest</VariablePrefix>
   <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
</ExtractVariables><br>

Then use a JS policy to compare/check. Assuming the tenant_list is a comma-separated string, the JS might look like this:

var authorizedTenants = context.getVariable('access_token.tenant_list').split(', ');
var tenantInUrl = context.getVariable('urirequest.tenantCode');
var found = (authorizedTenants.indexOf(tenantInUrl) >= 0);
context.setVariable('requested_tenant_is_authorized', found +''); //coerce to String

Then, in the policy flow, you can raise a fault if the tenant is not authorized:

<Step>
  <Name>OAuthV2-VerifyAccessToken</Name>
</Step>
<Step>
  <Name>ExtractVariables-1</Name>
</Step>
<Step>
  <Name>JS-CheckTenant</Name>
</Step>
<Step>
  <Condition>requested_tenant_is_authorized != "true"</Condition>
  <Name>RaiseFault-NotAuthorized</Name>
</Step>
 ..other steps follow here...

View solution in original post

10 REPLIES 10

Congrats on the release of the ODA APIs!

Requirements:

1. An API consumer (App registered in developer portal) should be issued a token only if : the appId(apiKey) is a registered in APIgee dev portal.

2.And, if the tenantApiCode and appId (apigee secret key) combination is authorized(This state sits in our Cloud can be accessed from an API) to server data it is trying to pull is authorized by the tenant admin.

OK, regarding the first requirement, that is how OAuth tokens work. You didn't say anything about authorizing individual users at the 3rd party integrators. So I'll assume that you're using a client-credentials flow. In that case, the 3rd party integrator will pass the client_id + client_secret (aka consumer_id + consumer_secret) to the /token endpoint at Apigee Edge. Apigee Edge then examines the credentials and if

  • the credentials are known
  • the creds are not revoked
  • the creds are not expired
  • the owning app is not marked "revoked"
  • the owning developer is not marked "inactive"

..then Apigee Edge will issue a token . This is all done by the magic policy called OAuthV2/GenerateAccessToken .

Regarding the tenantApiCode.... what you can do is, prior to requesting the generation of the access token, you can call the authorization API,which queries the tenants authorized per API Key. Then, attach the list of tenants as a custom attribute to the token.

On subsequent requests bearing that token, verification of the token will implicitly retrieve the list of tenants authorized for that token.

You can then enforce a check on the tenant-in-the-request against the list of tenants authorized for the appid. I think JavaScript would be the easiest way to do this.

This is really straightforward, and is a really common approach.


For token issuance, the OAuthV2/GeneratorAccessToken looks like this:

<OAuthV2 name='OAuthV2-GenerateAccessToken-CC'>
  <ExpiresIn>1800000</ExpiresIn>
  <RefreshTokenExpiresIn>691200000</RefreshTokenExpiresIn>
  <SupportedGrantTypes>
    <GrantType>client_credentials</GrantType>
  </SupportedGrantTypes>
  <GrantType>request.formparam.grant_type</GrantType>
  <Attributes>
    <Attribute name="tenant_list" ref="tenant_list_from_api_service" display="true"/>
  </Attributes>
  <GenerateResponse enabled='true'/>
</OAuthV2>

This policy assumes that the context variable "tenant_list" contains the list of tenants for this app (client id).

OK that's how you issue tokens. In subsequent arriving at the API Proxy you will want to verify tokens. Do that with OAuthV2/VerifyAccessToken. That step implicitly retrieves the list of tenants authorized for that token, and stores it into a context variable. Assuming you have the GenerateAccessToken policy configured as above, the variable name is accesstoken.tenant_list. Subsequently, you will want to check the tenant in the request against the tenant list in the token. So, you can use ExtractVariables to extract the tenantid from the request URI. Like this:

<ExtractVariables name="ExtractVariables-1">
   <DisplayName>Extract a portion of the url path</DisplayName>
   <Source>request</Source>
   <URIPath>
      <Pattern ignoreCase="true">/oda/apis/get/reports/assets/tenant/{tenantCode}/devicedistribution</Pattern>
   </URIPath>
   <VariablePrefix>urirequest</VariablePrefix>
   <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
</ExtractVariables><br>

Then use a JS policy to compare/check. Assuming the tenant_list is a comma-separated string, the JS might look like this:

var authorizedTenants = context.getVariable('access_token.tenant_list').split(', ');
var tenantInUrl = context.getVariable('urirequest.tenantCode');
var found = (authorizedTenants.indexOf(tenantInUrl) >= 0);
context.setVariable('requested_tenant_is_authorized', found +''); //coerce to String

Then, in the policy flow, you can raise a fault if the tenant is not authorized:

<Step>
  <Name>OAuthV2-VerifyAccessToken</Name>
</Step>
<Step>
  <Name>ExtractVariables-1</Name>
</Step>
<Step>
  <Name>JS-CheckTenant</Name>
</Step>
<Step>
  <Condition>requested_tenant_is_authorized != "true"</Condition>
  <Name>RaiseFault-NotAuthorized</Name>
</Step>
 ..other steps follow here...

Thanks @Dino. Great answer. Yes you can assume we do not have to deal with user auth. The integration is a cloud provider which manages their own users.

Most of the high level flow is clear to me. I am trying to understand specifics. You say this:

prior to requesting the generation of the access token, you can call the authorization API,which queries the tenants authorized per API Key


Integrator (3rd party) calls Edge oauth endpoint with their identity(apikey assuming?) to get access token. Where is this flow you mention above ? Does this happen within the access token call coming into Edge? Can you show with a sample how to call a custom REST target api(assume https://oda-api.plantronics-manager.com/{clientId}/authorizedTenants to get authorized tenants for the client_id via a apigee policy? Not sure if I see that step in your explanation. Thanks!

Also, can we expose an API with apigee which enforces API key pattern and auth token pattern together ? Guessing client_id will be apiKey replacement.

when I say

prior to requesting the generation of the access token, you can call the authorization API,which queries the tenants authorized per API Key

What I mean is, the Flow during token Generation can look like this:

  <Step>
    <Name>SC-MapApiKeyToTenantList</Name>
  </Step>
  <Step>
    <Name>JS-ExtractTenantListToVariable</Name>
  </Step>
  <Step>
    <Name>OAuth2-GenerateAccessToken-CC</Name>
  </Step>

The first policy is a ServiceCallout to your internal service that get authorized tenants for the client_id. [In fact it is not necessary to make a remote callout, as it is possible to associate custom attributes with API keys right within Apigee Edge. But if you already have an external service, then maybe a call out to the external service is convenient for you.]

The second policy is a Javascript that parses the response from the servicecallout to extract just the specific set of tenants, and set that list into a context variable. Eg, "tenant_list".

Finally the third policy generates a token and attaches the tenant list to the token as a custom attribute. This policy is configured as I described above.


can we expose an API with apigee which enforces API key pattern and auth token pattern together ? Guessing client_id will be apiKey replacement.

I don't know what you mean by "enforces". If you want to enforce some sort of client authentication or identity, you design your API to accept an OAuth2 token, or an API Key. There are other options, but those are the most popular. You should not use both. You should not require the client app to send both a key and a token - it does not make sense. That would be redundant.

Here's why: The token is generated *from* the API key. The way an app acquires a token is:

  • present credentials - apikey and secret - to the oauth endpoint
  • oauth endpoint generates and returns a token

When the app subsequently makes a new request bearing that token, Apigee Edge can validate it and at runtime determine the API key that was associated to the token; in other words the api key that was used to generate the token. Which determines the original app that requested the token. Do you see? The VerifyAccessToken policy will even populate a variable containing the client_id (aka apikey) upon successful verification of a token. So a valid token can be and is related to an originating API key, always.

Everything makes sense @Dino. Yes, we do not want both api key and token. I got that it does not makes sense. The last question I want to pick your brain is this. :

The first policy is a ServiceCallout to your internal service that get authorized tenants for the client_id. [In fact it is not necessary to make a remote callout, as it is possible to associate custom attributes with API keys right within Apigee Edge. But if you already have an external service, then maybe a call out to the external service is convenient for you.]


Is it possible to cache the response (tenant list) supported? Since I do not want the service call out to go our service for each oauth token validation. And, we(I think) cannot make it an attribute inside edge since the app's tenant level authorization can change anytime based on the tenant to app authorization workflow in our UI. So, ideally it will be good to cache the tenants allowed for the app every minute or so update this list doing the service call out at the end of expiry.

Thanks for all the answers. Very useful!



I will answer your question, and then give you a different idea.

Yes it is possible to cache things like the tenant list. Apigee Edge has a builtin cache, and there are Lookup and Populate policies. you can insert anything in there you like. It an be the entire response content, or some elaborated, manipulated version of the content, or something else. The cache key is flexible - it's up to you to design that.

However, I thnk you don't need the cache.

The idea is not to lookup the tenant list on each token VALIDATION, but on each token ISSUANCE. The tenant list for an app id can then be stored WITH THE TOKEN. Then, every time your API proxy validates the token, the tenant list is automatically brought into the request context, in the form of a context variable.

This is what the custom attribute is for. See my original example policy for OAuthV2/GenerateAccessToken, in the original answer.

yep @Dino makes sense. Once you clarified the lookup when issuing token than token auth, I got it. No more worrying about scaling our backend for oauth authorization 🙂 I come from Mashery(Traffic manager Development) before. And, I know the challenges:) Thanks again. I have a clear idea of how to implement this use case. Let me know anything I can do to give feedback for all the help.

Hi @Dino,

I'm trying to do a POC of the 1st part of the flow, where an external api is being called from the javascript code and the response is passed in the custom attribute in oauth policy.

Few things I'm unsure of is -

1)What should be the ref here for the attribute section?

<Attributes>

<Attribute name="tenant_list" ref="" display="true"/>

</Attributes>

2)In the Proxy flows I added a step before GenerateAccessTokenClient-CC and RefreshAccessToken to run the JS policy 1st -

<Step>

<Name>getTenantsSubs</Name>

</Step>

Is this right approach?

3)The actual JS code that does an external api call. Currently I'm seeing the custom attribute in the response but it's empty.

I'm attaching the proxy here. Can you please suggest fixes to this? oauth-rev2-2017-12-13.zip


oauth-rev2-2017-12-13.zip (3.9 kB)

Not applicable

Hi @Dino,

I'm trying to do a POC of the 1st part of the flow, where an external api is being called from the javascript code and the response is passed in the custom attribute in oauth policy.

Few things I'm unsure of is -

1)What should be the ref here for the attribute section?

<Attributes>

<Attribute name="tenant_list" ref="" display="true"/>

</Attributes>

2)In the Proxy flows I added a step before GenerateAccessTokenClient-CC and RefreshAccessToken to run the JS policy 1st -

<Step>

<Name>getTenantsSubs</Name>

</Step>

Is this right approach?

3)The actual JS code that does an external api call. Currently I'm seeing the custom attribute in the response but it's empty.

I'm attaching the proxy here. Can you please suggest fixes to this? oauth-rev2-2017-12-13.zip

hi - if you have a new question, please ask it with "Ask a Question".

You have posted this question as an Answer.

6147-ask-a-question.png