Azure API Management OAuth Proxy Policy

Azure API Management OAuth Proxy Policy

The OAuth Proxy is part of the Token Handler pattern that is a modern, secure solution for Single Page Applications. By using the Token Handler Pattern there is no need to manage and store any tokens in client side code, that is in the browser. The application authentication state relies instead on same-site session cookies. If you're not yet familiar with the pattern, then have a look at the architectural overview of the Token Handler pattern, which describes the two components:

  • The OAuth Agent that handles the OAuth flow and stores any access tokens in encrypted session cookies.
  • The OAuth Proxy that decrypts the session cookies and forwards the access tokens to the downstream API.

This code example shows how to implement an OAuth Proxy in an API Management Service in Microsoft Azure with the help of policies. Policies in the API Management (APIM) Service are an XML document with statements that define how to handle requests and responses. Consequently, they are the tool for validating tokens, headers and parameters of incoming requests before forwarding them to the backend such as the tasks of the OAuth Proxy in the Token Handler pattern.

Overview of the Setup

The API Management Service consists of several components, two of which are relevant in this context:

  • The API Gateway that receives and forwards every request
  • The Management Pane for configuring the APIs and policies of the gateway

Normally, the API Gateway just passes through requests to the downstream API without performing any changes. Policies provide a configuration to the API Gateway with statements that describe how to handle requests or responses. They allow for verification and alternation of the messages. The Management Pane offers an interface for writing the policies, including a code-editor for more sophisticated rules.

Policies are the natural choice for implementing security features. The policy presented in this example makes extensive use of policy expressions, that is the execution of C# statements. It performs request validation (checking allowed origin and CSRF tokens if required) and then decrypts the access token cookie to retrieve the actual access token. If the Phantom Token Pattern is used, then the policy can instruct the API Gateway to exchange the opaque token for a JWT. See Microsoft Azure API Management for a detailed description of a policy for token introspection.

Next, the OAuth Proxy policy states to overwrite the Authorization header of the request before forwarding it to the downstream API. The API response is returned to the caller without any modifications.

Deploying the Policy

Prerequisites

Design your system and identify the URLs of the OAuth Agent, the OAuth Proxy and client application. OAuth Agent and OAuth Proxy may share a domain, such as https://api.example.com whereas the client application, commonly a Single Page Application, may be deployed at https://app.example.com. Use the latter for setting up the trusted origin and support for CORS.

The policy requires a key to decrypt and verify cookies. You can use the following command to create a compatible key:

openssl rand 64 | base64

Note, that this key is normally shared by the OAuth Agent that generates the encrypted cookies.

To deploy the ARM template, install Azure CLI. For example, on macOS install the cli with homebrew. Once installed, log in with your account.

brew update && brew install azure-cli
az login

Make sure that there is a resource group available in Azure that your account is allowed to deploy resources to.

Deployed Resources

The template creates several resources:

Configure the policy and deployment with the help of the parameters supported by the template. Simply modify the file oauth-proxy-template/oauthproxydeploy.parameters.json and adapt the values (see the Configuration section). Choose a globally unique name for the API Management Service.

az deployment group create --resource-group my-resource-group --template-file oauth-proxy-template/oauthproxydeploy.json --parameters @oauth-proxy-template/oauthproxydeploy.parameters.json --mode incremental

Overwrite parameters from the parameter file by adding --parameters parameterName=newValue to the command.

az deployment group create --resource-group my-resource-group --template-file oauth-proxy-template/oauthproxydeploy.json --parameters @oauth-proxy-template/oauthproxydeploy.parameters.json --parameters apiManagementServiceName="oauthProxyTest" --mode incremental

Note that it can take long time for the command to finish, especially if the deployment results in a new instance.

Configuration

NameTypeDescriptionExample
apiManagementServiceNameStringThe name of the API Management Service instance that will be created by the template."oauthProxyApim"
publisherEmailStringThe email of the owner of the API Management Service. Azure sends for example a notification email to this address when the deployment is complete."developer@example.com"
publisherNameStringThe name of the owner of the API Management service."Dave Loper"
allowTokensBooleanIf set to true, then requests that already have a bearer token are passed straight through to APIs. This can be useful when web and mobile clients share the same API routes.true
trustedOriginStringThe web origin from which the OAuth Proxy will accept requests."https://app.example.com"
cookieNamePrefixStringThe prefix of the cookies that hold the encrypted access and CSRF tokens that are handled by the policy."oauth-proxy"
encryptionKeySecret StringBase64 encoded encryption key. This key is the master key for decrypting and verifying the integrity of the cookies."hydDIo7hOnUXnAzR3Y00y8r6SvfQG8PToNx9yALkKrhAozdBHAw6w4aLSlWowO5IHR9Q0k6BISHVDWcqwZvL7Q=="
usePhantomTokenBooleanSet to true, if the Phantom Token pattern is used and the API Gateway should exchange opaque tokens for JWTs.true
introspectionUrlStringThe URL of the introspection endpoint at the Identity Server that the API Gateway will call as part of the Phantom Token pattern to retrieve a JWT."https://idsvr.example.com"
clientIdStringThe client id used by the API Gateway when exchanging an opaque token for a JWT; part of the basic credentials required at the introspection endpoint."test-client"
clientSecretSecret StringThe secret used by the API Gateway when exchanging an opaque token for a JWT; part of the basic credentials required at the introspection endpoint."Secr3t!"

Policy Details

Most of the parameters of the template result in a named value under the API Management Service. In this way the policy can be easily configured during runtime. For example, simply modify the named value oauthProxyEncryptionKey to change or rollover the encryption key. The named values can also be used to extend the policy, for example, when setting up support for CORS.

Note, that the code just demonstrates how to implement the OAuth Proxy with the API Management Service in Azure. There are known limitations in the provided policy. For example, it does not cache any results. Caching is recommended when combining the OAuth Proxy with the Phantom Token pattern that requires to integrate the Curity Identity Server with Microsoft Azure API Management.

Cross Origin Requests (CORS)

The template adds support for CORS in the policy. The policy sets the CORS headers in the following way, where {{OAuthProxy-TrustedOrigin}} is the value of the configured trusted origin.

<!-- Set CORS headers for trusted origin-->
<cors allow-credentials="true">
   <allowed-origins>
       <origin>{{OAuthProxy-TrustedOrigin}}</origin>
   </allowed-origins>
   <allowed-methods preflight-result-max-age="86400">
       <method>*</method>
   </allowed-methods>
   <allowed-headers>
       <header>*</header>
   </allowed-headers>
   <expose-headers>
       <header>*</header>
   </expose-headers>
</cors>

The provided CORS snippet from Microsoft takes care of setting the corresponding headers, in particular it returns the appropriate Access-Control headers for preflight requests. Note that the default configuration of the policy uses wildcards for allowed methods and headers, but the responses will contain explicit values. That is according to the specifications which prohibits the use of wildcard values together with allow-credentials=true. The CORS-policy takes care of the details.

You can adapt the settings according to your needs. Search for the API Management Service with the name that you specified during the deployment. Navigate to the APIs page and list global policies by clicking on All APIs.

API Management Pane in Azure Portal

Open the policy editor for inbound processing policies and start editing the XML snippet for the CORS policy.

API Management Policy Code Editor

Integrating with an SPA and OAuth Agent

This implementation uses AES256-CBC with HMAC-SHA256. This is due to the limited set of .Net framework types available in the policy expression language. Make sure to use this policy together with an OAuth Agent that protects the cookies with AES256-CBC and HMAC-SHA256. Other examples of the Token Handler pattern may use AES256-CBG which provides built-in message integrity but the algorithm is not supported in APIM policies.

Testing

When doing integration tests, get hold of the cookie generated by the OAuth Agent and set up the OAuth Proxy with the appropriate key. However, when you have a key (either shared or generated, you can encrypt any data independently, for example with the help of openssl. The code example includes a simple script that runs openssl to encrypt a given message and creates a Base64Url-encoded string that can be placed in cookies. To encrypt a message, provide the encryption key and the string to the script like in the following example:

./test/encrypt.sh "hydDIo7hOnUXnAzR3Y00y8r6SvfQG8PToNx9yALkKrhAozdBHAw6w4aLSlWowO5IHR9Q0k6BISHVDWcqwZvL7Q==" "AccessTokenData"

Copy the output and use it as the value for an encrypted cookie. When testing the Phantom Token flow, run an integration test where the OAuth Agent creates the encrypted cookie and the Identity Server returns the corresponding JWT for the decrypted value.

With the encrypted cookie(s) in place, you can use Azure Portal for testing the policy. It provides a detailed report of the request processing which is very useful when developing a policy.

  1. Log in to the Azure Portal.
  2. Navigate to the API Management instance.
  3. Under APIs choose the API you want to debug.
  4. Open the Test tab.
  5. Select the API operation to debug.
  6. Add the required headers:
    1. Add Origin header and set the value the configured trusted origin.
    2. Add access token cookie, e.g. Cookie: oauth-proxy-at=xxxx where xxxx is the encrypted access token from the OAuth Agent (or the output from the encrypt script).
    3. If testing a data-changing method, add CSRF header and cookie, e.g. X-oauth-proxy-csrf: yyyy and Cookie: oauth-proxy-csrf=zzzz where zzzz is the encrypted value of yyyy.
  7. Send request.
  8. Check trace information in the Trace tab in the HTTP response.

Read Microsoft's tutorial on how to debug your APIs using request tracing.

Conclusion

Policies and provided policy expressions are powerful enough to implement the OAuth Proxy features by reading cookies, setting temporary variables, manipulate responses and requests. This code example shows just a basic approach for how to implement an OAuth Proxy. Policies have support for caching or key-vault integration which are both useful features for an OAuth Proxy implementation.