How to Use JWT Signer Plugin
Kong has many powerful plugins, among all of them, OIDC is no doubt the most complicated plugin considering it has over 200 parameters. JWT signer plugin might be runner-up as it has over 70 parameters. Both plugins are quite scary at first glance, but they are actually easy to use once you know what to look for.
In today’s post I will explain everything you need to know to use JWT signer plugin. You can find this plugin official documentation here.
Concepts
TL;DR: JWT signer plugin has the ability to authenticate and re-sign JWT token with two IDPs independently on a single plugin instance.
The trickiest part to understand this plugin is that both of its authentication and token re-signing features are optional. Also both features can be done TWICE with two different IDPs on a single plugin instance.
A common request flow would be:
Client send a request with a token to Kong route that has JWT signer plugin enabled.
JWT signer plugin will validate this token either by checking its signature (when passed in token is JWT) or via Introspection (when passed in token is an opaque token)
Once authentication is completed, Kong re-signs a new token with the same info on JWT or from introspection result with Kong’s own private key and then put this new token on the upstream request.
Common Questions
Why do you want to authenticate with two IDPs at the same time?
Imagine when requests go through a restricted network, a
channel_token
will be attached to the request automatically. Users can only consume the API when the requests have bothchannel_token
and users’access_token
.The
channel_token
helps to confirm a request comes from the restricted network and theaccess_token
helps to identify which user send in the request.What are the benefits of re-signing tokens?
Upstream server only need to trust Kong’s public key instead of keys from different IDPs.
It is easier to rotate key-sets. If you want Kong to generate a new key pair, you just need to call
POST kong:8001/jwt-signer/jwks/<JWK_ID>/rotate
.For IDPs that only issues opaque tokens, this feature helps the upstream server to avoid calling introspection endpoint in case they have restricted Internet access. The re-signed JWT tokens contains all information returned from introspection result, the upstream server can just decode and use the info on it.
Features
Let me break down these two features and tell you the minimum parameters you need to use.
Parameters for both
access_token
andchannel_token
are identical, I will use{token}
as a placeholder. For exampleconfig.{token}_issuer
representsconfig.access_token_issuer
andconfig.channel_token_issuer
.If you only need to validate one token, you can use
config.{token}_optional=true
to disable the other one. This plugin expects to validate both tokens by default.
Authentication
Signature verification
When the tokens issued by your IdP are in JWT format, you can use your IDP’s public key to validate these tokens. When validation pass, we can trust the information on the JWT token to be valid.
config.{token}_jwks_uri : This parameter is used to specify the uri of the IDP’s public key.
config.verify_{token}_signature : This is a Boolean value to turn on/off signature verification. It is true by default.
Signature verification is pretty straight forward. User just need to let Kong know where to find the public key.
JWT tokens does not need to be issued by IDP, you can generate your own key pairs. I’ve written a post before about generating key pairs to sign JWT token locally. You can find it here.
Introspection
If the tokens issued by your IdP are opaque tokens, you can validate and get token information from introspection endpoint. Different IDPs have different implementation/requirements for using introspection endpoint. Some might not even provide introspection endpoint when they are issuing opaque tokens (I am talking about you Auth0).
- config.{token}_introspection_endpoint : This parameter is used to specify the uri of IDP’s introspection endpoint.
- config.{token}_introspection_authorization : It is most likely you need to use some authorization headers for introspecting tokens. If you use client_credential flow, you need to put the value of
Basic base64encode('client_id:client_secret')
on this parameter. For example, if yourclient_id
andclient_secret
are bothadmin
, you need to set this parameter asBasic YWRtaW46YWRtaW4=
.
As long as you know what info your IDP introspection endpoint is expecting, you should be able to tweak plugin configs to make the introspection call successful.
For example you can useconfig.{token}_introspection_body_args
for passing additional body arguments and config.{token}_introspection_hint
to give a hint to the introspection endpoint what token you are passing in.
JWT token re-signing
You can control the re-signing process with below parameters.
config.{token}_keyset : If you use a url on this parameter, Kong will expect to load private key in JWK format from that url. If a string is given, Kong will generate a new keyset with the name of the string.
config.{token}_signing_algorithm : You can specify what signature algorithm you want to use for signing the new token. Kong use RS256 by default.
config.{token}_upstream_leeway : You can add/subtract expiry time of the re-signed token.
As you can see you have the ability to control how Kong signs the token but you can’t manipulate the claims on the new token. (except exp
claim)
Kong Enterprise supports add_claims and set_claims from 3.3 version.
This is to keep this plugin simple to avoid adding extra processing time to the request. It is better to request tokens with all the claims you need from IDP in the first place.
Other features
Consumer Mapping
The following parameters let you map a claim from JWT token or introspection result to a consumer.
config.{token}_consumer_claim
config.{token}_consumer_by
config.{token}_introspection_consumer_claim
config.{token}_introspection_consumer_by
These parameter names are self-explanatory. You probably don’t need to worry about *_consumer_by
parameters and only need to use *_consumer_claim
to map your consumer username or custom_id.
Claim value validation
There are two sets of parameters for claim value validations.
config.{token}_introspection_scopes_required
and config.{token}_introspection_scopes_claim
can be used to validate key:value pair on introspection results.
config.{token}_scopes_claim
and config.{token}_scopes_required
can be used to validate claims on JWT token.
config.{token}_scopes_claim
andconfig.{token}_introspection_scopes_claim
These two parameter supports nested claims. The array indices traverse the claim array.config.{token}_scopes_required
andconfig.{token}_introspection_scopes_required
These two parameter checks the related claim values and claim values are case sensitive.
- When [“scope1 scope2”] are in the same array indices, both scope1 AND scope2 need to be present on access token or introspection results.
- When [“scope1”, “scope2”] are in different array indices, either scope1 OR scope2 need to be present in access token or introspection results.
Let’s say my JWT token has below in payload
1 | { |
If I only want coffee lover to consume my API, I can set config.{token}_scopes_claim=["employee","favourites","beverage"]
and config.{token}_scopes_required=["coffee"]"
.
Demos
Prerequisite :
Kong Gateway running with an enterprise license.
In my demo I am running Kong Gateway 2.7.1.1
Kong uses default value for plugin parameters when they are not set. These default values are normally set to the best practise to help you using plugins easily. The default values should work for most cases but I strongly advise you to check the official doc to understand what default values are used.
Here are some common use cases:
Validate one JWT token and re-sign JWT token with Kong’s private key
1
2
3
4
5
6curl --request POST \
--url https://$KONG_ADMIN_API/plugins \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data name=jwt-signer \
--data config.access_token_jwks_uri=https://<Keycloak>/auth/realms/demo/protocol/openid-connect/certs \
--data config.channel_token_optional=trueIntrospect one opaque and re-sign JWT token with Kong’s private key
1
2
3
4
5
6
7curl --request POST \
--url https://$KONG_ADMIN_API/plugins \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data name=jwt-signer \
--data config.access_token_introspection_endpoint=https://<Curity>/oauth/v2/oauth-introspect \
--data 'config.access_token_introspection_authorization=Basic base64encode('client_id:client_secret')' \
--data config.channel_token_optional=trueValidate two tokens at the same time
You can combine settings above to validate two JWT token, introspect two opaque tokens or validate one JWT token and introspect one opaque token.
By default Kong looks for access_token in
Authorization:Bearer
orAuthorization: Basic
header. We need to useconfig.channel_token_request_header
to tell Kong where to look for the channel token.If we need channel token to be re-sign and sent to upstream, we need to use
config.channel_token_upstream_header
to set header to upstream. By defaultauthorization:bearer
will be used for sending re-signedaccess_token
to the upstream.1
2
3
4
5
6
7
8
9curl --request POST \
--url --url https://$KONG_ADMIN_API/plugins \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data name=jwt-signer \
--data config.access_token_introspection_endpoint=https://<Curity>/oauth/v2/oauth-introspect \
--data 'config.access_token_introspection_authorization=Basic base64encode('client_id:client_secret')' \
--data config.channel_token_jwks_uri=https://<Keycloak>/auth/realms/demo/protocol/openid-connect/certs \
--data config.channel_token_request_header=X-JWT-channel-client \
--data config.channel_token_upstream_header=X-JWT-channel-upstreamIn above example, I use introspection on
access_token
and validate JWT signature onchannel_token
. Kong will look for the channel token fromX-JWT-channel-client
header in the client request and will put re-signed channel token toX-JWT-channel-upstream
header on the upstream request.I sent my request as below
1
2
3
4curl --request GET \
--url https://$KONG_PROXY:8443/demo \
--header 'Authorization: Bearer <Opaque>' \
--header 'X-JWT-channel-client: <JWT_TOKEN>'Do not validate JWT token, only re-assign token with Kong’s private key
I don’t encourage you to skip token validation but you can turn off JWT validation when needed. Once it is turned off, Kong will re-sign any JWT token passed in and add it to upstream request.
1
2
3
4
5
6
7curl --request POST \
--url https://$KONG_ADMIN_API/plugins \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data name=jwt-signer \
--data config.channel_token_optional=true \
--data config.verify_access_token_signature=false \
--data config.verify_access_token_expiry=false
That’s all I want to show you today. As usual I can’t/don’t want to cover all plugin parameters but I am hoping this post can help you understanding the basics of this plugin and to start using it.
See you on the next one.