Token-Based Access Control With Kong, OPA and Curity
As APIs and microservices evolve, the architecture used to secure these resources must also mature. Utilizing a token-based architecture to protect APIs is a robust, secure and scalable approach, and it is also much safer than API keys or basic authentication. However, token-based architecture comes in varying maturity levels, as outlined by the API Security Maturity Model.
In this article, we’ll implement the Phantom Token Approach to achieve Level 3 of the API Security Maturity Model; Centralized Trust Using Claims. This approach externally uses opaque (reference) access tokens, exchanging them for a signed JSON Web Token (JWT) with scopes and claims in Kong Gateway. The system then passes that information onward to the upstream API.
Introspection is a way for a service to determine the state of a token and retrieve additional metadata about an active token.
When Kong Gateway is involved in the architecture, it typically performs the introspection. Kong Gateway receives an access token from a client requesting access to an API.
In an ideal scenario, we would use an opaque token if it’s coming from a public client. An opaque token is not required, but we strongly recommend not using JWTs as public tokens, as they are much more likely to contain Personal Identifiable Information (PII).
Even if JWTs don’t contain PII, developers will likely come to rely on the information within JWTs, and their applications will be at risk of breakage if the information issued in the JWT changes. Thus, it’s a much better practice to issue opaque tokens to public clients.
The Phantom Token Approach
When implementing the Phantom Token Approach, Kong Gateway will receive an opaque token and perform introspection. In the introspection process, Kong Gateway can send the application/JWT accept header. This header will make the introspection response from the Curity Identity Server be in the format of a JWT instead of the token data. This setting will allow Kong Gateway to perform a coarse-grained authorization by checking the JWT for scopes based on the configuration. If the required scopes are available, Kong Gateway will add the JWT to the Authorization header and forward the call to the upstream API. Then, the upstream API can consume the JWT as needed.
This approach allows for a more secure approach where an opaque token that does not contain any PII information to be issued to the client. When the client requests information from an API that potentially needs additional data, Kong Gateway can use the opaque token received and in turn, obtain a JWT that it can forward to the upstream API. The API will very likely need that additional information within the JWT to determine what data to return.
Curity has developed a specific plugin to handle introspection, especially related to the Phantom Token Approach. The Kong plugin is publicly available in the kong-phantom-token-plugin GitHub repository. This article describes how to set up an Integration with Kong Open Source and the configurations needed.
As mentioned earlier, this approach can also handle coarse-grained authorization using the JWT scope claim. This will allow Kong Gateway to quickly reject the call to the API if the correct scopes are not present in the token and prevent the call to the API.
Coarse-grained access control would probably be sufficient in many scenarios. Still, there will certainly be use cases where we need to consider additional parameters for authorizing access to data. These are scenarios where we can leverage Open Policy Agent (OPA) to implement a fine-grained access control policy.
Instead of calling the API, we can chain the Phantom Token Plugin to pass the JWT to the OPA Plugin. The OPA Plugin is responsible for making a callout to an instance of OPA that holds an access policy.
One example could be checking that the requesting user is the owner of the record requested. Or a separation of duty scenario where a user cannot approve a transaction that they also created. OPA with the Rego policy language is extremely powerful and can handle very complex policy expressions.
If the evaluation of the policy is successful, the flow will continue. Just like before, we added the JWT to the authorization header, which called the API.
This flow and its configuration are outlined in our API Authorization using Open Policy Agent and Kong article. It includes a dockerized example environment using open source or community edition software.
- Obtain an access token from the Curity Identity Server.
- The access token passes to the API endpoint exposed by Kong Gateway.
- Kong Gateway receives the access token.
- It calls the introspection endpoint.
- The Curity Identity Server issues a JWT.
- The Phantom Token Plugin performs a coarse-grained authorization check using the scopes received in the JWT.
- If the coarse-grained check is approved, the OPA plugin calls an instance of OPA.
- The decision is returned to Kong Gateway.
- If authorization is allowed, Kong Gateway calls the upstream API.
Try It Out
Kong Gateway and OPA are both open source software and readily available. Curity provides a Community Edition of the Curity Identity Server that is free for anyone to use.
Check out the articles mentioned above to get an environment running. Obtain an access token from the Curity Identity Server and use a tool like Insomnia or OAuth Tools to make a request to the service exposed by Kong Gateway (httpbin.org in the below example).
GET /httpbin/get HTTP/1.1
Authorization: Bearer 12033b19-23bf-4e8c-8257-324358872f82
Suppose the Phantom Token Plugin authorizes the call after checking that the JWT received contains the configured scopes. In that case, the system adds JWT to the authorization header and forwards the call to the API.
httpbin.org is an HTTP request and response test service that can simply echo back what it received in the request. The below sample response shows that the authorization header received is a JWT and not the opaque token that was sent in the original request to Kong Gateway.
“Authorization”: “Bearer eyJraWQiOiIxMjM0NDI1OTgxIiwieDV0IjoiT3JWU3ZmWmVZTmVVV3dXUEViWWNWSjY1WFgwIiwiYWxnIjoiUlMyNTYifQ.eyJqdGkiOiJmZWU4OTk2MC01Y2JjLTRhMDgtYjUzMC00ZDVmNTczOTNiZDIiLCJkZWxlZ2F0aW9uSWQiOiI1ZWM4Njg1ZC1iZGRmLTQyMjEtYjIwYi00MWZkMGVlYzRiYTQiLCJleHAiOjE2MjcwNzI2OTcsIm5iZiI6MTYyNzA3MjM5Nywic2NvcGUiOiJvcGVuaWQiLCJpc3MiOiJodHRwczovL2U4MGNjMmNhZjEyYjo4NDQzL29hdXRoL3YyL29hdXRoLWFub255bW91cyIsInN1YiI6ImFsaWNlIiwiYXVkIjoid3d3IiwiaWF0IjoxNjI3MDcyMzk3LCJwdXJwb3NlIjoiYWNjZXNzX3Rva2VuIn0.iC-Ze_OHPRFsmB0zydTVydQ0tVa3MIVaCnjYtuATxBepgqrbUEQGBLpGG1FLomO_MsOQLHa605CMYw-43ZoI7LM6iloyzZbYiCqzzjTmW_KWXN4IDpgDOW6F-i2-JR4lxWXNCCwYqY3pYQDu5VpyqjWDGH2GDeffavfYn_SS95RSafUVE6binZPI7_01722duM4Nzu0HOSKADWkjgdEXH-hT5dBx9HaHQjmUyJpbE50lXEzWOxWLdrvqqwXhCNNy5xIUUtFcbqH-9VzZC760iL_zgX_wVuHs1f5ZcJ6OZzQvHAoxMWeXTtOHr_4KZ257UFnT6bfUs0QnISYLWi64Qg”,
“origin”: “18.104.22.168, 22.214.171.124”,
Issuing JWTs for public clients is a bad practice, and doing so could leak PII or cause instability if the structure of the JWT changes. It is better to issue opaque (by reference) tokens and have Kong Gateway introspect the opaque token and exchange it for a JWT that you can use internally (i.e., passed to the upstream API).
Or, for a more fine-grained access control approach, include OPA to evaluate an access control policy to determine if access should be allowed or not.
Here are some additional articles that cover these topics and other integration approaches for the Curity Identity Server, Kong Gateway and OPA:
- Kong Gateway | Guides | Curity
- The API Security Maturity Model | Curity
- The Phantom Token Approach | Curity
- Integrating with Kong Open Source | Curity
- Integrating with Kong Enterprise | Curity
- API Authorization using Open Policy Agent and Kong | Curity
- Setting up OpenID Connect Authentication in the Kong Developer Portal | Curity
- Kong OpenID Connect Plugin
- OAuth 2.0 Token Introspection – rfc7662