Using third-party OAuth tokens

This page applies to Apigee and Apigee hybrid.

View Apigee Edge documentation.

In this topic, we'll discuss how to import externally generated access tokens, refresh tokens, or auth codes into the Apigee token store. You can use this technique if you would like to configure Apigee to validate tokens that are generated outside of Apigee.

In the usual case, Apigee will generate and store an OAuth token, and return it to the calling application. The calling app then presents that token back to Apigee when requesting service, and Apigee - via the OAuthV2 policy with Operation = VerifyAccessToken - will verify that the token is valid. This topic describes how you can configure Apigee to store an OAuth token that was generated elsewhere, while keeping the token verification part the same, just as if the token was generated by Apigee.

Example

If you want to see a working example that illustrates the technique described in this topic, take a look at the Apigee Delegated Token Management sample.

What is this?

Suppose you have an existing authorization system in place, and you would like to use the token or code values generated by that system in place of the OAuth2 token or code values that Apigee generates. You can then make secure API proxy requests with the substituted token or code, and Apigee will validate them as if they were generated by Apigee.

Some Background

In the usual case, Apigee generates a token by producing a random string of letters and numbers. Apigee associates to that token, other data such as the time the token was issued, the expiry, the list of API Products for which the token is valid, and the scope. All of this information can be returned in a response automatically generated by the OAuthV2 policy configured with Operation = GenerateAccessToken. The response looks like this:

{
  "issued_at": "1469735625687",
  "application_name": "06947a86-919e-4ca3-ac72-036723b18231",
  "scope": "urn://example.com/read",
  "status": "approved",
  "api_product_list": "[implicit-test]",
  "api_product_list_json": ["implicit-test"],
  "expires_in": "1799", //--in seconds
  "developer.email": "joe@weathersample.com",
  "token_type": "BearerToken",
  "client_id": "U9AC66e9YFyI1yqaXgUF8H6b9wUN1TLk",
  "access_token": "zBC90HhCGmGlaMBWeZAai2s3za5j",
  "organization_name": "myorg",
  "refresh_token_expires_in": "0", //--in seconds
  "refresh_count": "0"
}

The access_token value is used by Apigee to retrieve the token metadata. For example, suppose an API proxy request includes the bearer token zBC90HhCGmGlaMBWeZAai2s3za5j. Using the token value, Apigee retrieves the token metadata to determine if the token is valid or not.

By following the steps described here, you can configure Apigee to store a token whose access_token value was generated by an external service. For example, suppose you have a system external to Apigee that generates tokens of the form "TOKEN-<16 random numbers>" . In that case, the full token metadata stored by Apigee might be:

{
  "issued_at": "1469735625687",
  "application_name": "06947a86-919e-4ca3-ac72-036723b18231",
  "scope": "urn://example.com/read",
  "status": "approved",
  "api_product_list": "[implicit-test]",
  "api_product_list_json": ["implicit-test"],
  "expires_in": "1799", //--in seconds
  "developer.email": "joe@weathersample.com",
  "token_type": "BearerToken",
  "client_id": "U9AC66e9YFyI1yqaXgUF8H6b9wUN1TLk",
  "access_token": "TOKEN-1092837373654221",
  "organization_name": "myorg",
  "refresh_token_expires_in": "0", //--in seconds
  "refresh_count": "0"
}

In this case, an app could make a request to an API proxy, carrying the bearer token TOKEN-1092837373654221, and Apigee will be able to validate it. You can apply a similar import pattern to authorization codes and refresh tokens.

Let's Talk about Validating Client Credentials

One pre-requisite to generating a token is validating the requesting client. By default, the OAuthV2/GenerateAccessToken policy in Apigee implicitly verifies the client credentials. Normally in a request for an OAuthV2 token, the client_id and client_secret are passed in the Authorization header, encoded via HTTP Basic Authorization (colon-concatenated, then base64-encoded). The OAuthV2/GenerateAccessToken policy in Apigee decodes that header and looks up the client_id, and verifies that the passed-in client_secret is valid for that client_id. This works if the credentials are known to Apigee - in other words there is a Developer App stored within Apigee which contains a credential, which itself contains the given client_id and client_secret.

In the case that the client credentials are not to be validated by Apigee, you must design your API Proxy, before it generates a token, to explicitly validate the client via some other means. Often this is via a ServiceCallout policy that connects to a remote endpoint in your network.

One way or the other, either implicitly or explicitly, you need to ensure that the API Proxy that generates tokens first validates the client credentials. Keep in mind that validating the client is independent of generating the access token. You can configure Apigee to do both, or to do one or the other, or neither.

If you want the OAuthV2/GenerateAccessToken policy in Apigee to validate the client credentials against the Apigee store, set the <ExternalAuthorization> element to false inside the policy configuration, or omit it entirely. If you want to use an external authorization service to explicitly validate the client credentials, set <ExternalAuthorization> to true.

Though Apigee may not validate the client credentials, it is still necessary for the client_id to be known and managed by Apigee. Every access_token in Apigee, whether generated by Apigee or generated by an external system and then imported into Apigee, must be associated to a client application - indicated by the client_id. So even in the case where the OAuthV2/GenerateAccessToken policy in Apigee will not validate that the client_id and client_secret match, the policy will validate that the client_id is valid, present, and not revoked. So as a pre-requisite setup step, you may have to import client_id's via the Apigee administrative API.

Policy Flow for third-party OAuth on Apigee

To use tokens from third-party OAuth systems in Apigee, the flow for generating access tokens should follow one of the following patterns.

External Validation of Client Credentials

  1. ServiceCallout to Verify the inbound client credentials, and acquire an external token.
  2. ExtractVariables or a JavaScript step to extract the externally-generated token from the response.
  3. AssignMessage to set the special well-known-variable called oauth_external_authorization_status. The value must be true to indicate the client credentials are valid.
  4. OAuthV2/GenerateAccessToken with the <ExternalAuthorization> element set to true, and at least one of <ExternalAccessToken>, <ExternalRefreshToken>, or <ExternalAuthorizationCode>.

Internal Validation of Client Credentials

  • ServiceCallout to acquire an external token.
  • ExtractVariables or a JavaScript step to extract the externally-generated token from the response.
  • OAuthV2/GenerateAccessToken with the <ExternalAuthorization> element set to false, and at least one of <ExternalAccessToken>, <ExternalRefreshToken>, or <ExternalAuthorizationCode>.

Notes on the flow and policy configuration

  • In the case that you wish to use an external system to validate client credentials, it is up to you to develop a policy flow that does what is necessary. Normally you would use a ServiceCallout policy to send the externally recognized credentials to the external authentication service. The external authentication service would typically return a response and, if the credentials are valid, also an access token.

  • After the ServiceCallout, the API proxy needs to parse the response to extract the validity status, as well as the externally generated access_token and possibly the refresh_token.

  • In the OAuthV2/GenerateAccessToken policy, set the <StoreToken> element to true, and set the <ExternalAuthorization> element to true or false as appropriate.

    When the OAuthV2/GenerateAccessToken policy executes, it reads the variable oauth_external_authorization_status. If the variable is set and the value is true, then Apigee does not attempt to validate the client credentials. If the variable is not set or the value is not true, then Apigee will attempt to validate client credentials.

  • There are three elements for the OAuthV2 policy that allow you to specify the external data to import: <ExternalAccessToken>, <ExternalRefreshToken>, and <ExternalAuthorizationCode>. Each of these elements accepts a flow variable. The Apigee policy will read that variable to find the externally-generated access token, refresh token, or authorization code. It's up to you to implement policies and logic to place the external tokens or codes in the appropriate variables.

    For example, the following configuration in the OAuthV2 policy tells Apigee to look for the token in a context variable named external_token.

    <ExternalAccessToken>external_token</ExternalAccessToken>
    

    You would need to also have a previous step that sets that variable.

  • Regarding setting the oauth_external_authorization_status variable, a common technique for setting this variable is to use an AssignMessage policy with the AssignVariable element, like this:

    <AssignMessage name="AssignMessage-SetVariable">
        <DisplayName>Assign Message - Set Variable</DisplayName>
        <AssignVariable>
            <Name>oauth_external_authorization_status</Name>
            <Value>true</Value>
        </AssignVariable>
        <IgnoreUnresolvedVariables>true</IgnoreUnresolvedVariables>
    </AssignMessage>
    

    Remember, this policy must fall before the OAuthV2 policy with Operation = GenerateAccessToken.

Example OAuthV2 policy

The following OAuthV2 policy generates an access token given that Apigee finds a token value in the flow variable external_access_token.

<OAuthV2 name="OAuth-v20-Store-External-Token">
    <DisplayName>OAuth v2.0 1</DisplayName>
    <Attributes/>
    <ExternalAccessToken>external_access_token</ExternalAccessToken>
    <ExternalAuthorization>true</ExternalAuthorization>
    <Operation>GenerateAccessToken</Operation>
    <GenerateResponse enabled="true">
        <Format>FORM_PARAM</Format>
    </GenerateResponse>
    <ReuseRefreshToken>false</ReuseRefreshToken>
    <StoreToken>true</StoreToken>
    <SupportedGrantTypes>
        <GrantType>client_credentials</GrantType>
    </SupportedGrantTypes>
    <ExpiresIn ref='flow.variable'>2400000</ExpiresIn>
</OAuthV2>

In theory, you could apply this pattern with any third-party OAuth2 authorization service.