Trusted Subsystem Security Pattern in an API Gateway Architecture
By Johann Nallathamby
- 26 Sep, 2018
One of the challenges in an API gateway architecture is flowing claims that establish the end user’s identity and the client’s identity at the API provider layer. This is also known as identity propagation. Establishing these identities at this layer is important in order to:
- Control access to fine-grained backend services based on entitlements of the end user and client
- Filter data in the API provider’s response according to data entitlements
- Audit and keep records of data access
Entitlements are generally established based on a combination of job roles of the users and business policy, privacy, and compliance requirements.
There are two popular security patterns to fulfill the above requirements in a API gateway architecture.
- Impersonation and Delegation
- Trusted Subsystems
In this post I won’t be going into a detailed analysis of these two patterns, nor will I compare and contrast the two. Instead I will focus on the trusted subsystem pattern which is the more widely implemented security pattern in API gateway architectures among. I will also discuss how it’s implemented in today’s organizations.
Figure 1 Flowing claims to downstream resources
In a multi-layered application architecture, downstream services often trust the upstream services to perform a specific set of security functionality. This is known as the trusted subsystem security pattern. The set of functionality can include authenticating the caller, authorizing the caller to perform the particular application functionality, asserting claims to the downstream services regarding the caller identity and the functionality it requested to perform.
Critical Design Goals
Among many other design goals of the trusted subsystem security pattern, the following are the most important:
- The ability for services to mutually and positively identify each other
- Enable an upstream service to propagate the identity of the caller to downstream services
- Protect the integrity and confidentiality of the flowed claims
In an API gateway architecture, today we see that the following methods are commonly used by the downstream resources to achieve positive identification of the upstream gateway:
- Basic authentication — Need to securely manage credentials for each backend.
- 2-way Transport Layer Security (TLS) — Client certificate management will be an overhead. May not be able to selectively disable for certain clients who can’t support 2-way TLS. TLS might be terminated at the proxy/load balancer layer, which means the client is not identified at the actual API provider layer.
- IPSec — May be difficult to scale upstream service horizontally.
- IP allowlisting — Might be difficult if static IPs cannot be obtained for upstream services. Might also be difficult to scale upstream service horizontally.
- 2-legged OAuth2 — Using “client_credentials” grant type between the API gateway and API provider. This has the overhead of managing the client ID and client secret securely for each backend API provider at the API gateway, and also handling the expiry of access tokens.
- Signed tokens — Generating a signed token for each API invocation can be an overhead. Caching the tokens could help improve performance. The tokens could be self-issued or issued by a third-party. E.g. Signed JSON Web Token (JWT).
- No auth — And finally it may not be a surprise that we still find deployments where the API gateway is not authenticating at all with the downstream resources, but sending a plain text header to identify itself. Since identity propagation often occurs within the organization’s network, many administrators naively assume that it is safe to propagate the data unprotected. But in reality, most security breaches occur within the organization’s internal network.
Types of Propagated Identity Tokens
In the trusted subsystem security pattern not all claims are asserted by the immediate upstream trusted subsystem. The downstream services may require additional claims asserted by different trusted parties in order to verify and support the genuineness of the flowed claims, allowing these services to securely process the request.
There are 3 categories of propagated identity tokens in a trusted subsystem architecture.
- Trusted subsystem generated identity tokens — Self-issued tokens by the trusted subsystem, to assert the caller’s identity. Generally these tokens are self-verifiable and self-contained. For example, a JWT containing the end user identity and required claims, signed and issued by the API gateway. The downstream service performs data origin authentication of the token, and assumes the trusted subsystem has authenticated the caller whose identity is in the propagated token.
- Third-party generated identity tokens - These tokens are issued by a third party, that is trusted to perform certain specialized security functions. For example, a JWT issued from the userinfo endpoint of an OpenID Connect authorization server or from the introspection endpoint of an OAuth2 authorization server. While the downstream services trust the upstream subsystem to assert the caller, the third-party generated identity token may serve as evidence to an additional set of security requirements. For example, a downstream service has to be able to verify that the end user has been recently authenticated by a trusted third party. This reduces the window of time available for a compromised trusted service to propagate false identity claims. These tokens can be categorized into types such as “bearer”, “proof-of-possession/holder-of-key” and “sender-vouches”. Bearer tokens can be further categorized as “opaque” (tokens by reference) or “self-contained” (tokens by value).
- User self-signed tokens - These tokens are generated by the caller itself, and serve as additional evidence that can be used to correlate a request with the caller. For example, a user self-signed JWT which includes the user’s public certificate, timestamp and a message identifier to identify the chain of requests initiated by the caller.
In all three categories of tokens described here, the downstream resource relies on the integrity of the trusted subsystem, which requests to perform a certain functionality on behalf of the asserted identity. The end user’s identity is simply flowed as part of the message from the trusted subsystem. It is not possible to detect if the trusted subsystem substituted one user’s identity in place of another identity (perhaps cached) for malicious reasons.
Another approach to flowing identities to the API provider in the trusted subsystem pattern is through identity mapping. Sometimes a service may take a given identity and, by itself or using a trusted third party such as a security token service (STS), map the given identity into another related identity, optionally with the credential to authenticate. The new identity is then used to gain access to downstream resources. This is sometimes required when the downstream resources only recognize the transformed identity. The resulting tokens from the mapping process also can be identified to be one of the propagated identity token types.
Protecting the Integrity and Confidentiality of Claims
Protecting the integrity of the claims is important so that no man-in-the-middle can modify the identity information that is in transit. Similarly protecting the confidentiality of the claims is also important so that no one can eavesdrop on sensitive information. To protect claims, the following methods are commonly used:
- TLS - Provides transport level integrity and confidentiality. Plain text headers are sent to flow claims to downstream resources.
- Signed tokens -This provides message level integrity. E.g. Signed JWT.
- Encrypted tokens - This provides message level confidentiality. E.g. encrypted JWT.
It goes without saying, we still find deployments where there is no integrity or confidentiality protection between the API gateway and backend resources.
So far we’ve looked at multiple methods for identification, integrity protection and confidentiality protection. You can combine any of the methods under the different design goals above to implement your API gateway.
Optimizations with Self-contained Access Tokens
One of the key benefits of the self-contained access token pattern is that it reduces the network communication latency by reducing the number of remote calls going over the network from the API gateway to the OAuth2 authorization server. However, if we avoid calling the OAuth2 introspection or OpenID Connect userinfo endpoints, we won't be able to get claims to be sent to the API provider layer.
To avoid this, we need to make sure we encode all the claims necessary for the resources in the self-contained access token at the OAuth2 authorization server while issuing it, and then at the API gateway layer during the API invocation phase we have the following options that are commonly used:
- Send the self-contained access token as it is (decoded) to the backend. The ‘aud’ attribute of the JWT in this case also should have the API gateway and the API provider as audiences. However in order to allow this, the API providers in your use case must be in the same trust domain as the API gateway, so that the access tokens won’t be compromised. This is an example of using a third-party generated identity token. Since the JWT itself can provide integrity protection and confidentiality, TLS is not mandatory to protect the token.
- Simply remove the signature from the self-contained access token, which makes it an unsigned JWT, and forward it to the API provider just for data entitlement. We purposefully remove the signature to avoid revealing the access token to the API provider in case it does not belong to the same trust domain as the API gateway. The ‘aud’ attribute should have the API gateway and the API provider as audiences. The API gateway should use one of the methods for identification listed above. This is an example of using a trusted subsystem generated token. Integrity and confidentiality protection has to be provided by TLS.
- A solution that is better than removing the signature from the JWT, as mentioned in the previous point, is to append a random string to the JWT. For example, the access token may be a Base64 encoded String of the following JSON structure:
The token_id is a JWT and can be extracted and sent to the API provider layer.
- Extract all the claims from the self-contained access token without going back to the OAuth2 authorization server, and forward it using either plaintext headers or self-issued JWTs. Generating a JWT will incur additional performance overhead at the API gateway for each API invocation. To improve performance we can cache the JWT. If using plain text headers, integrity and confidentiality protection has to be provided by TLS. This again is an example of using a trusted subsystem generated identity token.
- In case a self-issued JWT is used to authenticate the API gateway to the API provider, you could encode the claims in the self-contained access token to the same JWT to avoid sending multiple JWTs to the API provider. This will also eliminate the duplicate overhead of generating two signed JWT tokens at the API gateway. This is an example of using a trusted subsystem generated identity token. Since the JWT itself can provide integrity protection and confidentiality, TLS is not mandatory to protect the token.
In this article we have taken a closer look at what is identity propagation, its importance in an API gateway architecture, and how it is widely achieved using the trusted subsystem security pattern.
The WSO2 API Manager and WSO2 Identity Server are two open source products, distributed under the Apache 2.0 license, that possess a powerful API gateway component and a security token service component respectively, that give them the ability to support various kinds of trusted subsystem requirements as described in this article.