JWT Claims With Rate Limiting in Kong
In Kong, plugins can be thought of as policy enforcers. In the case of rate limiting, Kong offers two plugins:
Both plugins can limit requests per consumer, route, service or globally. Configuring the same plugin is also possible on a more than level. When this occurs, an order of precedence is used to determine which configuration to run. With this capability, it is possible to apply fine-grained policy control.
In this article, we cover an advanced use case. We previously demonstrated how to secure APIs with OpenID connect, where we showed how to apply authorization with scopes. In this article, we show how to apply rate limiting with OpenID Connect.
Rate limit counter per consumer – without a Kong consumer
First, we recreate the entities we had in the previous blog post. These are:
- A service to a backend
- A route where Kong will listen.
We use HTTPie:
http localhost:8001/services name=rate-limiting-jwt-service url=http://httpbin.org/anything
http -f localhost:8001/services/rate-limiting-jwt-service/routes name=rate-limiting-jwt paths=/rate-limiting-jwt
We can now test the endpoint in Kong by doing the following:
Next, we configure the openid-connect plugin to secure the API users. In this configuration, we do not require a specific scope:
http -f localhost:8001/routes/rate-limiting-jwt/plugins \
We want to apply rate-limiting on a per user basis, without having to a Consumer object for each user in Kong. The documentation for the rate-limiting-advanced plugin should show us how to accomplish this. In the parameters section, we see the config.identifier configuration:
|config.identifier optional default value: consumer||How to define the rate limit key. Can be ip, credential, consumer, service, or header.|
Unfortunately, none of these will be a perfect fit. A good guess would be to use the parameter: “credential”. Sadly, this will not work as the tokens are short-lived. This means that the credential object will change frequently.
There is an interesting parameter to consider for the identifier configuration. The option to use a header. We will need a unique header per end-user. If we have it, then our rate limiting will work well. Do we have such a header?
It turns out the OpenID Connect plugin provides the option to extract any claim(s) from the token and create headers based on them.
The configuration parameters:
can help us accomplish what we need for our use case. The first parameter describes which claim(s) we want to export (as a comma-separated array) and the second, defines how the name(s) of the created header(s) shall be.
The Open ID Connect plugin configuration we have applied earlier in this blog post, already has these setting:
This means that we can now apply our rate-limiting plugin on the created route now and every user will get his own counter based on their email address. This counter would then be used by the rate limiting plugin, based on the JWT token. Let us try this out.
We first need to set the rate limiting policy:
http -f localhost:8001/routes/rate-limiting-jwt/plugins \
Now let us go to KeyCloak. For testing purposes, we create two example users:
- blog_user1 / veryComplexPa55word
- blog_user2 / veryComplexPa55word
Let us test our work. We suggest you open the URL in two different browsers (for example, Chrome and Firefox) and use one user per browser at http://localhost:8000/rate-limiting-jwt. This will help guarantee that no cached tokens or sessions will interfere with each other in the same browser. On the response you will notice that our backend, httpbin.org, is helpful as it tells us which headers have been added so the response. Among the headers, you should find something like this:
Is it possible to have one counter for multiple end-users, for example a company or a department?
This is a common question. It is often needed when an allowance to an organization is needed. Meaning that all developers at a partner or consuming organization are sharing the same limit in one ‘global’ counter.
We can solve for this by changing the relevant setting in the OpenID Connect plugin from
… config.upstream_headers_claims=email \ …
… config.upstream_headers_claims=groups \ …
With this change, we now extract a unique group name instead of the end user’s email. It would make more sense to provide a different name to the header like X-Extracted-Group or similar. But this is a demo, afterall.
💡 You will need to configure your IdP in a similar way such that is presents the group claim somewhere in the token.
💡 This example setup depends on a user being in only one group as the groups claim otherwise might be something like group1,group2
What if we need a specific rate-limit for a specific consumer?
For this use case, a ‘per-consumer’ counter needs to be stored somewhere.
The above scenario works well if all end-users have the same limit. They each will have individual counters. Such a default limit is typically meant for the majority of users.
The OpenID Connect plugin can be configured so that a Consumer object is optional? This means that we can, but do not have to, create a consumer object for some users.
We create a consumer for our firstname.lastname@example.org, then we attach a rate-limiting plugin configuration to this consumer. Since this is a more specific plugin setting, it takes precedence.
http localhost:8001/consumers email@example.com
http localhost:firstname.lastname@example.org/plugins \
We hope you enjoyed these examples. We demonstrated the flexibility of applying rate limits when using the OpenID connect plugin, but taking advantage of Kong’s ability to extract information from tokens, as well as configuring plugins at a variety of levels of granularity. Happy rate-limiting.
If you want to see this a live demonstration of this example, please request a demo.