21.12.2023 - Alexander Suchier - 9 min read Part 3: Token Exchange On-Behalf-Of Kong - The Gateway without Limitations

In the last blog, I provided a solution on how to overcome the character limit when logging. This time, I would like to show how to implement an OAuth 2.0-based On-Behalf-Of (delegation) grant flow. Such complex token orchestration tasks can be easily handled on the API Gateway while ensuring the highest security standards which even make zero-trust architectures (ZTA) possible in the first place.

Overview

In my previous blog articles, I concluded that Kong is the API Gateway without limits. In this technical blog, I would like to provide a solution for an enhanced token management flow for token exchange. The exchange flow is called On-Behalf-Of grant type, OBO for short. Once again, the Kong Gateway proves its great expansion possibilities.

The OBO flow is not part of the standard OAuth 2.0 specification (RFC 6749) of the Internet Engineering Task Force (IETF). Currently, it seems to be that Microsoft has a deviating OBO implementation as part of their Microsoft Azure Active Directory (AAD) product, as described and implemented in the OBO Microsoft documentation. In the following, I will refer to the OBO solution offered by Microsoft AAD (recently Azure AD was renamed to Microsoft Entra ID, but for this blog I will stick to the previous well-known name and the acronym AAD). While the IETF has extended its standard to include OAuth 2.0 Token Exchange (RFC 8693), but AAD does not utilize the officially introduced grant type urn:ietf:params:oauth:grant-type:token-exchange. Microsoft’s implementation deviates from the official documentation promoted as a standard in 2020. Still, the delegation idea remains the same (at this point I would like to point out an excellent token exchange YT video by Sascha Preibisch).

The OBO flow enables a resource server to call another protected resource server in the background without any user interaction. This approach is particularly valuable when there are two or more resource servers that trust the same authorization server and contribute to the logic of a single application through the request chain. The idea is to propagate the delegated user identity and appropriate permissions seamlessly throughout the chain in the background. It allows the exchange of tokens from “external” to “internal” structures. Traditionally, such calls are made as machine-to-machine requests using an access token obtained via the Client Credentials grant type. However, without OBO, the user context is lost when making these machine-to-machine requests.

A further great advantage of the AAD solution is that the requested token can not only be a JSON Web Token (JWT), but also a Security Assertion Markup Language (SAML) v1.0 or v2.0 token. There are still a lot of systems that can only get along with a SAML token. Therefore, it is an ideal solution for exchanging the security token format. It is important to note that the AAD OBO flow works only for user principles and not for service principles.

The diagram below illustrates the general principle with an API Gateway acting as an intermediary for the OBO grant type. This detail from the overall process sequence flow assumes that user authentication and authorization have been completed, and that the authorization server (AAD) issued a security token that may have been used in previous resource server calls.

test

The diagram conveys the OBO idea in the context of an intermediary API Gateway. To allow the API Gateway to use the OBO grant type, the authorization server must be properly configured. Discussing this configuration exceeds the scope of this blog, so please refer to the official configuration information available in the AAD documentation or look for other OBO articles that focus on AAD configuration (e.g. at Cloud Matter). Kong offers plugins for serving the standard authorization grant flows as described in the standard IETF specification (RFC 6749). Yet, after thorough investigation and consultation with Kong’s advisors, there is currently no official and third-party solution for implementing the OBO grant type. Let’s explore how we can overcome this limitation.

Problem

Consider a scenario where a frontend application has received a token via the authorization code grant (or implicit) flow and is communicating with services via an API Gateway. The gateway applies the security policies and distributes the token to the services that accept this token. Now a service should be called in the service-to-service chain that is subject to the security rules of another AAD registered application and only accepts SAML v2.0 tokens. However, you want to preserve the original user context and pass it on so that the called service can perform an authorization check on the original user identity. To do this, the existing token has to be exchanged for the existing user identity and converted to a new scoped SAML format. However, this complexity should not be imposed on each API caller but should be performed at a central switching point – the API Gateway. Is this achievable with the Kong Gateway? Absolutely, it is possible!

Solution

Kong is built on top of NGINX using the Lua-based OpenResty framework to enable API Gateway features (for more details, refer to my Kong introduction blog). As such, Kong is a Lua application designed to load and execute Lua modules (commonly referred to as plugins). These plugins interact with the request/response objects or streams via the Plugin Development Kit (PDK) to implement arbitrary logic. The PDK, which provides a rich set of Lua functions, allows anyone to create their own custom plugins.

That said, the solution to our problem of missing Kong functionality is to write our own OBO custom plugin (by the way, minor enhancements requiring only few lines of Lua code can also be implemented in so-called Kong Functions, formerly called Serverless Functions). If writing custom plugins is a new topic for you, please read the chapters in the documentation “Develop Custom Plugins” and watch the excellent plugin YT video by Lokesh Chechani.
However, it should come as no surprise that I have already written a Lua OBO custom plugin. I am happy to share it with you in case you also suffer from loss of OBO functionality. Please look at the NTT DATA GitHub repository for the plugin source code here.

Usage

The plugin priority determines when the plugin is executed. The plugin runs in the NGINX access phase with priority 800, therefore its execution in the processing pipeline follows the entry and authentication plugins, which can first check the entry token for validity. A valid entry bearer token in the Authorization header is important because this token needs to be exchanged on the authorization server via the OBO plugin.

The plugin takes the token from the request authorization header and executes an OBO on the AAD authorization server according to the specified plugin configuration. A method other than the authorization request header field for the token transfer is not supported by the plugin, as outlined in the IETF specification (RFC 6750).

In addition to the possibly required network and optional caching configuration, the actual OBO configuration must be complete. The following DB-less configuration excerpt shows a minimal running configuration that implements the OBO with a shared secret (please note that the plugin currently does not implement the OBO with certificate).

plugins:
  - name: oauth2-on-behalf-of
    tags: ["oauth2-on-behalf-of"]
    service: <configured service entity>
    enabled: true
    config:
      enable_proxy: true
      https_proxy: <proxy URL>
      https_verify: false
      enable_client_certificate: false
      token_endpoint: https://login.microsoftonline.com/<tenant id>/oauth2/v2.0/token
      client_id: "<client id>"
      client_secret: "<client secret>"
      scope:
      - <requested scope>
      requested_token_type: "urn:ietf:params:oauth:token-type:saml2"
      enable_caching: true
      enable_factor_ttl: true
      ttl_factor: 0.75
      stopwatch: false

The configuration shown above enables an HTTPS proxy configuration to reach the AAD authorization server. No client certificate is required. The OBO AAD endpoint is taken from the token_endpoint configuration parameter (the tenant is a path parameter). The client_id and client_secret are AAD-generated and assigned to the application client during the registration process. The scope permissions list the desired scopes as a string array. The requested_token_type parameter requires an official token format type for the token to be issued (Microsoft adheres to the official specification, see RFC 8693). It is possible to issue a JWT, SAML1 or SAML2 token format type. The schema definition of the OBO plugin contains a description of each parameter and the default setting. To pass the next penetration test, the determined token is rendered unusable for logging above the debug logging level by removing the signature (JWT, SAML1 and SAML2). If the token cannot be fully logged due to the existing logging character limitation, the previously introduced chunking module can be used (coding is commented out in the Lua handler module).

The OBO plugin has its own stopwatch and exhibits fluctuating runtimes between 250 and 350 milliseconds in my Google Kubernetes Engine (GKE) test environment when token caching is disabled. The entire plugin runtime is almost entirely due to the AAD invocation (in OAuth2 terms the call to the authorization server). The URL *.microsoftonline.com (port HTTPS/443) is used to import/export AAD data. If caching is enabled and the token is cached, the plugin runtime at Kong info logging level is approximately 1 millisecond. The cache key is formed from the configured AAD appli-cation client ID (client_id) and a unique user identifier claim (user_identifier_claim) which is determined from the token (customizable to your requirements).

Conclusion

Token management is complex but essential for communication between services. Therefore, easy-to-configure and customizable plugins are important for this task. The discussed OBO plugin fills a gap in Kong and enables a token exchange directly at the Kong Gateway. The plugin is implemented in Lua for stability and performance. The presented plugin will solve your OBO token exchange problems with AAD. To reiterate my opening statement, with the Kong Gateway you will not run into any limitations.

Credits

Title image by Gorodenkoff on Shutterstock

Alexander Suchier

Senior Managing Technical Consultant and Kong Champion