20.02.2024 - Alexander Suchier - 11 min read Part 4: SAML 2.0 Bearer Assertion Flow for OAuth 2.0 Kong - The Gateway without Limitations

My last blog provided a solution for implementing an OAuth 2.0-based On-Behalf-Of (delegation) grant flow. This time it’s about how to implement a Security Assertion Markup Language (SAML) 2.0 Profile for OAuth 2.0 Client Authentication and Authorization Grants. This flow allows OAuth 2.0 clients to obtain access tokens by presenting SAML 2.0 assertions as a form of authentication. This rather extended grant flow broadens the scope for exchanging authentication and authorization data between different parties with seamless management, all achieved through the API Gateway. Once again, the API Gateway enables the highest and most diverse security standards by providing the ability to run such a special grant flow. Therefore, the Kong Gateway is an important security enabler and plays a crucial role in establishing the foundation of a zero trust architecture (ZTA) even under heterogeneous security conditions.

Overview

My previous blog articles ended by saying that Kong is the API Gateway without limits. In this technical blog post, I will share a solution for another extended token management flow for token exchange. The exchange flow is called SAML 2.0 Bearer Assertion flow for OAuth 2.0, or SAML 2.0 Bearer Assertion for short. The Kong Gateway proves again and again its great extensibility.

The SAML 2.0 Bearer Assertion flow is not part of the OAuth 2.0 specification (RFC 6749) from the Internet Engineering Task Force (IETF), which describes some “standard” ways for clients to request and receive tokens. Later, the SAML 2.0 Bearer Assertion (RFC 7522) extended this base specification by describing a way to exchange SAML assertions, an XML-based security token, for OAuth tokens. The specification covers only Security Assertion Markup Language (SAML) v2.0 tokens; SAML v1.0 tokens are not considered. The SAML 2.0 Bearer Assertion RFC was promoted as a standard in 2015.

The essential core is that a client can use the resource owner’s SAML 2.0 Bearer Assertion, presented as a bearer token, to obtain an access token from the authorization server without having to interact with the resource owner again. It also allows authorization servers to trust authorization grants from third-party Identity Providers (IdP). In our context, the client is represented by the API Gateway, which performs this exchange of a valid and signed SAML assertion for an OAuth 2.0 access token. This RFC is particularly useful for integrating OAuth 2.0 with systems that use SAML 2.0 for identity and authentication purposes, and for seamlessly propagating the delegated user identity through the request chain in the background.

RFC 7522 supports two mechanisms for obtaining an OAuth token. In the following, I will refer to the SAML 2.0 Bearer Assertion grant type urn:ietf:params:oauth:grant-type:saml2-bearer. The specification also provides a SAML assertion for client authentication when the client needs to authenticate itself to the authorization server (urn:ietf:params:oauth:client-assertion-type:saml2-bearer), which is outside the scope of this blog.

The challenge and reason for this blog post was the need to exchange tokens with an SAP system to enable access to REST-based OData (Open Data Protocol) services. To do this, the SAP authorization server receives a SAML 2.0 token issued by Microsoft Entra ID (formerly Azure Active Directory, AAD), which is exchanged for an SAP-compliant access token. The SAP OData service can then be called with the access token in the request. Therefore, I will stick to this requirement and refer to some SAP documentation (in this SAP YT video there is another general explanation of the SAML assertion bearer flow in the SAP environment). However, many other sources show that the specification is actually being used in the field in non-SAP environments (e.g. this link shows an exchange with Salesforce, also explained in this video by Steve Simpson). There are several prerequisites for token exchange to work. Instead of listing them here, I will refer you to the corresponding SAP documentation of the SAML 2.0 Bearer Assertion. This reference summarizes all the requirements and shows the implementation in the SAP environment.

The following diagram illustrates the general principle with an API gateway acting as an intermediary for the SAML 2.0 Bearer Assertion grant type. It details the overall process sequence flow and assumes that user authentication and authorization have been completed. The authorization server (AAD) has already issued a SAML 2.0 token (more on this below).

The diagram shows the exchange of a SAML 2.0 Bearer Assertion for an OAuth2 access token in the context of an intermediary API gateway. In order for the API gateway to use the SAML 2.0 Bearer Assertion grant type, the authorization server must be properly configured. Discussing this configuration is beyond the scope of this blog, so please refer to the official configuration information available from SAP sources. Kong has plugins to handle the standard authorization grant flows as described in the standard IETF specification (RFC 6749). However, after thorough research, there is currently no official and third-party solution for implementing the SAML 2.0 Bearer Assertion grant type. Let’s explore how we can overcome this limitation.

Problem

Let’s say a front-end application has received a token via the authorization code grant (or implicit) flow and is communicating with services through an API gateway. The gateway applies the security policies and distributes the token to the services that accept this validated token.

So far so good, my challenge was a service-to-service calling scenario where the calling service comes with an AAD token and wants to call an SAP OData service via the API gateway. The difficulty was that the attached SAP OData service only accepts access tokens from the SAP authorization server. To make matters worse, while the front-end applications work with AAD JWT tokens, the SAP authorization server only accepts SAML 2.0 tokens for token exchange.

To handle this at the gateway, the incoming AAD JWT token must be exchanged for the existing user identity and converted to a new, scoped SAML 2.0 token format. Next, the SAP authorization server must be called with the newly obtained SAML 2.0 token to receive a scoped SAP access token in exchange. With this SAP access token, the gateway can finally call up the SAP OData service, which can also perform an authorization check on the original user identity (ZTA key principle: “never trust, always verify”). However, this complexity of token handling should not be imposed on each API caller, but should be performed at a central switching point - the API Gateway. Impossible with the Kong Gateway? No, this is possible!

Solution

The solution is to reuse the OBO plugin described in the previous blog together with a new SAML 2.0 Bearer Assertion plugin to be implemented. The OBO plugin conjures up a SAML 2.0 token in the existing user context, while the subsequent SAML 2.0 Bearer Assertion plugin procures the SAP access token in exchange for the SAML 2.0 token. The transfer point between the two plugins in the Kong processing pipeline is the authorization header; the OBO plugin takes the OAuth Bearer token and replaces it with the SAML 2.0 token, which in turn is used by the SAML 2.0 Bearer Assertion plugin. This type of token handoff also ensures individual use of the plugins for all use cases where only one of the plugins is required.

If successful, the SAML 2.0 Bearer Assertion plugin with the SAP authorization server configuration will generate a random string access token associated with authorization values stored on the SAP side. This is a valid approach because the OAuth2 standard does not specify a format for tokens. Therefore, it is not a self-contained token like the JWT, but a kind of reference token. Reference access tokens are opaque strings used in OAuth 2.0 for delegated authorization. The details associated with the token are stored on the authorization server, and the SAP OData service can be accessed with an opaque bearer token. The SAP OData API, as a protected resource, uses the bearer reference access token to look up the associated authorization information.

The OBO plugin can process the same input OAuth 2.0 access token twice, the response is a SAML assertion with a new ID and a countdown on the experies_in and ext_expires_in timeout response parameters (and of course the validity period in the token itself). Instead, the SAML 2.0 Bearer Assertion plugin can only take one and the same input SAML 2.0 token once, otherwise you will get an SAP error message like “Specified permission grant is invalid. Assertion was already consumed. More information in kernel traces or SAP Note 1688545”.

The following diagram outlines the OBO and SAML 2.0 Bearer Assertion flow on the gateway.

Plugins can add any kind of missing functionality to the gateway. Therefore, we just need to design and implement the outlined SAML 2.0 Bearer Assertion plugin and place it behind the OBO plugin in the Kong processing pipeline. In my last OBO blog, I have already discussed the general plugin programming, so we will not repeat that part.

However, it should come as no surprise that I also wrote a custom Lua SAML 2.0 Bearer Assertion custom plugin. I am happy to share it with you in case you are also suffering from the loss of SAML 2.0 Bearer Assertion functionality. Please see 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 700, so its execution in the processing pipeline follows the Kong entry and authentication plugins, which can first validate the entry token (in my case, the incoming AAD JWT access token is validated by the OpenID Connect plugin and then converted to SAML 2.0 by the OBO plugin). A valid SAML 2.0 Bearer token entry in the authorization header is important because this token must be exchanged on the authorization server via the SAML 2.0 Bearer Assertion plugin. As described, in my case the OBO plugin, which is responsible for generating the valid SAML 2.0 token, is preempted (priority 800). In general, the OBO plugin is optional; the SAML 2.0 token can also be provided directly by the calling client as a bearer token.

The SAML 2.0 Bearer Assertion plugin takes the SAML 2.0 token from the authorization request header and performs a token exchange on the authorization server according to the specified plugin configuration. A method other than the authorization request header field for token transfer is not supported by the plugin.

In addition to any required network and optional caching configuration, the actual SAML 2.0 Bearer Assertion configuration must be performed. The following DB-less configuration snippet shows a minimal running configuration that implements the SAML 2.0 Bearer Assertion.

plugins:
  - name: oauth2-saml2-bearer
    tags: ["oauth2-saml2-bearer"]
    service: <configured service entity>
    enabled: true
    config:
      enable_proxy: true
      https_proxy: <proxy URL>
      https_verify: false
      enable_client_certificate: false
      token_endpoint: https://<host:port>/sap/bc/sec/oauth2/token
      enable_basic_authn: true
      basic_authn_username: <basic authentication user>
      basic_authn_password: <basic authentication password>
      client_id: "<client id>"
      format: json
      scope:
      - <requested scope>
      enable_caching: true
      enable_factor_ttl: true
      ttl_factor: 0.75
      stopwatch: false

The configuration shown above allows an HTTPS proxy configuration to reach the authorization server for my use case. No client certificate is required. The SAML 2.0 Bearer Assertion SAP endpoint is taken from the token_endpoint configuration parameter. The SAP authorization server requires additional basic authentication protection. Therefore, a technical SAP user name and password must be provided.

The client_id is the client ID assigned by SAP. The required response format is JSON (urlencoded and xml format are also possible). The scope permissions list the desired scopes as a string array. The SAML 2.0 Bearer Assertion plugin schema definition includes a description of each parameter and the default setting.

The plugin also has an option to handle the CSRF protection required for manipulating (non-GET) requests. SAP requires a CSRF token and corresponding cookies for these OData requests. This link gives a good explanation about the CSRF topic. But you also have the option to let the consumer manage these parameters (in this case leave the default setting of the enable_scrf_protection parameter disabled).

To pass the next penetration test, the determined access token will be rendered unusable for logging above the debug logging level. If the token cannot be fully logged due to the existing logging character limit, the previously introduced chunking module can be used (coding is commented out in the Lua module).

The SAML 2.0 Bearer Assertion plugin has its own stopwatch and shows fluctuating runtimes between about 200 and 600 milliseconds in my Google Kubernetes Engine (GKE) test environment when token caching is disabled. The plugin runtime is almost entirely due to the SAP invocation (in OAuth2 terms, the call to the authorization server). If the caching configuration is enabled and the token is already cached, the plugin runtime at the Kong info logging level is about 2 milliseconds. The cache key is formed from the configured client ID (client_id) and the unique SAML assertion ID determined from the incoming token (easily changeable to suit your needs).

Conclusion

Token management is complicated, but essential for communication between services in modern zero trust architectures. Therefore, easy to configure and customizable plugins are important for this task. The discussed SAML 2.0 Bearer Assertion plugin fills a gap in Kong and enables token exchange directly at the Kong Gateway. The plugin is implemented in Lua for stability and performance reasons.

The presented plugin will solve your SAML 2.0 Bearer Assertion token exchange problems. It takes XML-based SAML 2.0 tokens and returns OAuth 2.0 access tokens in exchange. Again, going back to my opening statement, with the Kong Gateway you will not run into any limitations.

Beyond words: Special thanks to Savas Akgol, Markus Remmet and Hans-Jürgen May for their collaborative efforts and their yielded expertise in complex AAD and SAP configuration.

Credits

Title image by mikute on Shutterstock

Alexander Suchier

Senior Managing Technical Consultant and Kong Champion