OAuth2 Token Exchange RFC8693

The OAuth2 Token Exchange 8693 RFC defines a protocol for exchanging security tokens from OAuth2 authorisation servers.

With a rising popularity of micro-service patterns, it’s almost a given that the API you are calling is actually being fronted by a middleware service. Aside from load balancers, your API call will likely be passing through an open source API gateway like Tyk, rather than accessing the service directly.

This post presents typical mechanisms for API gateways to not only authenticate and authorise themselves, but also propagate user information and permissions to the underlying services which require them. It discusses their shortcomings, and then presents how the OAuth2 Token Exchange protocol can mitigate these shortcomings and improve API security overall.

OAuth2 is a delegation protocol

OAuth is a delegation protocol. Each actor has their own separate identity. In this ridiculously simplified diagram, we can see several actors: Alice/Bob, a browser, an app and the API. In OAuth2 speak, they are known as the Resource Owner(s), User Agent, Client Application and Resource Servers respectively. When Alice wants to perform any operation with her personal data, the API can’t just blindly return that data to the app. Rather, there is a protocol by which Alice needs to identify and authenticate herself, then delegate some of her rights to the app. The app also has its own identity, so once it has authenticated itself, it may then act as an agent on behalf of Alice.

 

When the app sends a request to the API to request Alice’s personal data, it does so using a bearer token. This token can be a reference or self-contained token, the details are not so important for the purposes of this post, but let’s assume that this token contains some claims including the id of the app as well as Alice’s user id. This is great, because for as long as there is a mechanism for the API to validate the authenticity of this token, and can therefore trust this metadata, it can effectively make a decision as to whether it may perform some protected operation on Alice’s data.

Introducing an API gateway complicates things

Once you got your head around the protocols and the flows – we are off to the races – but not so fast… It’s never that simple. We have an API gateway deployed right in the middle between the app and the API in order to protect that API from abuse. By introducing the gateway how does the API know who is logged in?

 

Let’s break this down a little into a few different strategies:

Auth Proxy

One way in which we can achieve this, is by trusting the gateway implicitly, then transparently passing the app’s access token through to the API directly. I guess it kind of works, but…

The access token contains an audience claim (aud), which specifies who this access token is intended to be read by. This can either be the app, or the gateway, but not both. Now in the Auth Proxy scenario, the gateway effectively is acting as a “Man in the middle”, and is trusting an access token which was not intended for it.

If the API became or is directly accessible by the app, then it means that the same access token used on the gateway can also be used directly on the API. This means that the gateway can relatively easily be bypassed and presents a potential attack vector, without extra mitigating layers of security.

OAuth2 Client Credentials

In this flow – we are considerably more secure. The gateway has its own credentials to access the API. The API knows to only trust traffic coming via the gateway as the gateway will be using its own access token. If any request is received by the API, or the API were to accidentally be made public, still, only the gateway would be able to access any protected data.

The problem here however is that regardless whether Alice or Bob is logged into the app, the gateway is always using its own credentials to access the API.

This means several things. Firstly, the gateway can validate the access token presented by the app. Once it has done that, it can use its own credentials to proxy the request on to the API. The API has to grant the gateway almost super-user privileges to perform any operation on any user. Why? Because it becomes impossible to propagate Alice’s userID and her delegated permissions to the app through to the API. This information is lost and by the time the request gets to the API, the API will not even know who the logged in user is. If Alice was an employee, and Bob a manager, this means that Alice could potentially give herself a pay rise FTW!

If we wanted to propagate user and role information to the API, ensuring that only Alice could access her own data, we would need to extract the UserID and role information or delegated permissions from the app’s access token, then use the gateway to inject this information as some magic request headers in the request to the API. The API would then need to be modified or coded to read these headers, in order to apply this business logic. This is a pattern which many gateways and other solutions recommend, as the gateway is now having to perform some level of business logic. The gateway would also need to be programmed to remove this magic header – as it could potentially be possible for the App to inject that header before it even got to the gateway, presenting potentially another attack vector.

I could go through a bunch of other tactics / strategies, but… Let us fast forward a bit to the point of this post. I hope that you all get that there has not really been a solid and scalable solution for effectively what is a pretty complex problem.

OAuth2 Token Exchange

In this flow – everything is exactly the same for the gateway using the Client Credentials flow, as the gateway has its own identity.

The difference however is that when the gateway authenticates (exchanges its credentials for a bearer token) with the authorization server, it also passes a bunch of other fields as part of the request.

grant_type: "urn:ietf:params:oauth:grant-type:token-exchange"
subject_token: "ORIGINAL_APPS_ACCESS_TOKEN"

Rather than a client_credentials grant, it is invoking a urn:ietf:params:oauth:grant-type:token-exchange grant. And passing the access token presented by the app along with the request. That means that when Alice uses an app to request her data, and Bob uses an app to access his data, the gateway will obtain a unique token for each request.

In the following snippet, you will find a sample request from the gateway to the authorisation server.

POST /token

grant_type: "urn:ietf:params:oauth:grant-type:token-exchange"
code: "a130cb70-af3c-44f6-a030-779b042cfaec.a5e5796a-0c99-4b5b-bf62-19fb7ea20ec3.8be6e388-cc31-441c-9b88-386c74ae2381"
client_id: "gateway"
client_secret: "GATEWAY_CLIENT_SECRET"
subject_token: "APP_ACCESS_TOKEN"

This means that the gateway obtains its own access token for each logged in user. The gateway becomes the authorized party, And its permissions (scopes) are dynamic, based on the permissions of the user who logged in. Let’s take a look at the metadata for the gateway’s access token for the API.

{
  "exp": 1606174134,
  "iat": 1606173834,
  "auth_time": 1606173730,
  "jti": "7368999a-0b78-47c5-abcb-1dc5a59b649d",
  "iss": "https://MY.OPENID.ISSUER",
  "aud": "api",                                   <---- 1
  "sub": "57e7837a-c490-4d80-91d5-723e92ff6ebe",
  "typ": "Bearer",
  "azp": "gateway",                               <---- 2
  "acr": "1",
  "realm_access": {
    "roles": [
      "offline_access",
      "uma_authorization"
    ]
  },
  "resource_access": {
    "account": {                   	              <---- 3
      "roles": [
        "manage-account",
        "manage-account-links",
        "view-profile"
      ]
    }
  },
  "scope": "profile email",                       <----- 4
  "email_verified": true,
  "name": "Alice",
  "preferred_username": "[email protected]",      <----- 5
  "given_name": "Alice",
  "email": "[email protected]"
}

1. The API can validate that the access token was intended for its consumption
2. The API can validate that this token is coming from the gateway and it has not been bypassed
3. The API can validate that Alice has delegated her permissions and these have been propagated to the gateway
4. The API can validate the scopes which were delegated by Alice also
5. The API can identify that it is Alice who is the logged in user

Conclusion

In conclusion, I believe that this is an extremely elegant way of utilising the OAuth2 Framework to propagate identity and permissions securely through a chain of different actors, and if adopted by API gateways, goes a long way toward standardising API authentication and authorisation mechanisms.

If the OAuth2 Token Exchange is a protocol you are currently using, or are interested in, please do get in touch, I would love to hear from you.