- Craft a custom JWT using the input Authentication properties
- Load the private JWK from the plugin's configuration and convert it into a PEM format
- Sign the JWT with the PEM string for building a JWS
- Add the custom JWT to an HTTP Request Header backend API
The plugin x-custom-jwt
doesn't check the validity of the input itself (neither checking of JWT signature & JWT expiration, nor user/password checking, nor checking Client TLS checking, nor api key checking). So it's mandatory to use this plugin in conjunction with one of the Kong's security plugins:
FORM PARAMETER | DEFAULT | DESCRIPTION |
---|---|---|
config.apikey_header | apikey | The Http header name to get the apiKey for Key Authentication |
config.bearer_clientid_claim | clientId | The claim name to extract the clientId (from a JWT) for Authentication Bearer |
config.upstream_custom_jwt_header | X-Custom-Jwt | The Http header name where to drop the custom JWT in the upstream request. It overrides any existing value. If the value is Authorization the Bearer type is added in the value |
config.downstream_custom_jwt_header | N/A | The Http header name where to drop the custom JWT in the downstream response. It overrides any existing value |
config.expires_in | 1800 | Number of seconds for the exp (expiration) claim which are added to the current time |
config.iss | https://kong-gateway:8443/x-custom-jwt | The iss (issuer) claim that identifies the Kong Gateway that issued the custom JWT |
config.jku | https://kong-gateway:8443/x-custom-jwt/jwks | The jku (JWK set Url) that points to a Kong Gateway route for delivering the well-known location of JWKs |
config.private_jwk | {"kty": "RSA","kid": "kong",...<CHANGE_ME>} | The JWK private key to sign the custom JWT. The format is JSON. RS256, RS512, SH256, SH512 algorithms are supported |
config.verbose | false | Append to the Consumer a detailed message in case of error |
-- Try to find one by one an Authentication given by the Consumer
find "Authorization: Bearer" header
if not "Authorization: Bearer" then
find "Authorization: Basic" header
end
if not "Authorization: Bearer" and not "Authorization: Basic" then
find "mTLS Client Certificate"
end
if not "Authorization: Bearer" and not "Authorization: Basic" and not "mTLS Client Certificate" then
find "apikey" header
end
if not "Authorization: Bearer" and not "Authorization: Basic" and not "mTLS Client Certificate" and not "apikey" then
-- The Consumer's request is blocked
return "HTTP 401", "You are not authorized to access to this service"
end
-- If the Consumer sends a correct Authentication, we craft the 'x-custom-jwt' JWT
-- The 'client_id' claim has a value depending of the Consumer's Authentication
if "Authorization: Bearer" then
-- Copy all the content of the AT (given by 'Authorization: Bearer')
x-custom-jwt.payload = AT.payload
x-custom-jwt.payload.client_id = x-custom-jwt.payload[plugin_conf.bearer_clientid_claim]
else if "Authorization: Basic" then
-- Copy the username of the Basic Auth
x-custom-jwt.payload.client_id = AT.payload.username
else if "mTLS Client Certificate" then
-- Copy the entire subjectDN (all distinguished names) of the Client Certificate
x-custom-jwt.payload.client_id = subjectDN
else if "apikey" then
-- Copy the 'apikey' value
x-custom-jwt.payload.client_id = apiKeyValue
end
-- If the 'x-custom-jwt.client_id' is not set
if not x-custom-jwt.payload.client_id then
-- The Consumer's request is blocked
return "HTTP 401", "You are not authorized to consume this service. Internal Error"
end
-- Header values for all Authentication methods
x-custom-jwt.header.typ = "JWT",
x-custom-jwt.header.alg = "<JWK.alg>", -- Got from the alg of private JWK
x-custom-jwt.header.kid = "<JWK.kid>", -- Got from the kid of private JWK
x-custom-jwt.header.jku = "<jku>" -- Got from the plugin Configuration
-- Common claims for all Authentication methods
x-custom-jwt.payload.iss = "<iss>" -- Got from the plugin Configuration
x-custom-jwt.payload.iat = "<current time>"
x-custom-jwt.payload.exp = x-custom-jwt.payload.iat + "<expires_in>" -- Got from the plugin Configuration
x-custom-jwt.payload.aud = "<url>" -- the Backend_Api URL
x-custom-jwt.payload.jti = "<uuid>" -- Generation of a 'Universally unique identifier'
-- 'act.sub' claim
x-custom-jwt.payload.act.client_id = "<kong-consumer-custom-id>" or "<kong-consumer-id>" -- Set by security plugins (OIDC, Basic Auth, Key Authentication, Mutual TLS Auth, etc.)
-- Sign the JWT with a private JWK (set in the plugin configuration) for building a JWS
jws_x_custom_jwt = jwt:sign (x-custom-jwt, private_jwk)
-- Add the custom JWT in an HTTP header to the request's headers before sending the request to the Backend API
kong.service.request.set_header(plugin_conf.upstream_custom_jwt_header, jws_x_custom_jwt)
In this repo, there is the konnect.yaml decK file related to the prerequisites and following examples. If you prefrer you can do deck gateway sync
and avoid manual declaration as explained below.
- Install the Kong Gateway
- Install the
x-custom-jwt
plugin by following the documentation, here - Install http.ie
- Prepare the RSA256 JWK for getting the Public and Private Keypair
- You can use the JWK keypair provided in this repo:
- JWKS Public Keys (JSON Web Key Set) Public Key: RS256-jwks-public.json
- JWK Private Key: RS256-jwk-private.json
- Or create your own JWK keypair: go for instance to the site https://mkjwk.org/ and configure the online tool with following values:
- key Size:
2048
- Key Use:
Signature
- Algorithm:
RS256
- Key-ID (kid):
kong
- key Size:
- Click on Generate, copy to clipboard the
Public and Private Keypair
(i.e. Private Key) and thePublic Key
- Create a Route to deliver the public JWKS
- The Route has the following properties:
- name=
x-custom-jwt-jwks
- path=
/x-custom-jwt/jwks
- Click on
Save
- name=
- Add the
Request Termination
plugin to the Route with:config.status_code
=200
config.content_type
=application/json
config.body
=copy/paste the content of RS256-jwks-public.json OrConfig.Body
=ThePublic JWK Key
must be pasted from https://mkjwk.org/ and add"keys": [
property for having a JWKS. If needed, adapt thekid
to a custom value. JWKS Structure:{ "keys": [ { ***** CHANGE ME WITH THE PUBLIC JWK ***** } ] }
- Click on
Save
- Add the
CORS
plugin to the Route with:- config.origins=
*
- Click on
Save
- config.origins=
- Create a Gateway Service
- For
httpbin
service, add a Gateway Service with:- name=
httpbin
- URL=
http://httpbin.apim.eu/anything
- Click on
Save
- name=
- Add a Route to the Service with:
- name=
httpbin
- path=
/httpbin
- Click on
Save
- name=
- Add
x-custom-jwt
plugin to the Service with:- config.bearer_clientid_claim=
clientId
or** Replace with the right claim for having the proper Kong consumer reconciliation **
- config.iss=
<adapt the URL to your environment>
(example: https://kong-gateway:8443/x-custom-jwt) - config.jku=
<adapt the URL to your environment>
(example: https://kong-gateway:8443/x-custom-jwt/jwks) - config.private_jwk=copy/paste the content of RS256-jwk-private.json Or
- config.private_jwk=paste the
Public and Private Keypair
from https://mkjwk.org/. If needed, adapt thekid
to a custom value; thekid
value must be the same as defined inPrerequisites
heading (see the configuration ofRequest Termination
plugin) - config.verbose=
true
- config.bearer_clientid_claim=
- Create a Consumer with:
- Username=
[email protected]
- Custom Id=
[email protected]
- Create a
[email protected]
Client in your IdP Server for Example #1
- Open the
httpbin
Service - Create a new Route:
- name=
oidc
- path=
/oidc
- Add
OpenId Connect
plugin to the Route with:
- config.client_id=
** Replace with your Client Id **
- config.client_secret=
** Replace with your Client Secret **
- config.issuer:
** Replace with your /.well-known/openid-configuration URL **
- config.auth_methods:
client_credentials
+introspection
- config.consumer_claim =
clientId
or** Replace with the right claim for having the proper Kong consumer reconciliation **
- Test
Request
:orhttp -a [email protected]:<**YOUR_PASSWORD**> kong-gateway:8000/oidc
http kong-gateway:8000/oidc Authorization:'Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJxOEVFR3YweE9FQkt3eFNJYVZDNGpHTWxVcF8yWURhS1pfMVdZNHV3b2lRIn0.eyJleHAiOjE3MTI2ODU3MTYsImlhdCI6MTcxMjY4NTQxNiwianRpIjoiYWI2MmQwNjUtNDYyNy00NDllLTk4ZDAtNTA0MGYwYjI4OTNhIiwiaXNzIjoiaHR0cHM6Ly9zc28uYXBpbS5ldTo4NDQzL2F1dGgvcmVhbG1zL0plcm9tZSIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJjYzE2M2ZmNS1iZmMxLTRkNmYtYTFjMS02YjAzZTI5NWY2MmYiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJjb250YWN0QGtvbmdocS5jb20iLCJhY3IiOiIxIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwiZGVmYXVsdC1yb2xlcy1qZXJvbWUiLCJ1bWFfYXV0aG9yaXphdGlvbiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImNsaWVudElkIjoiY29udGFjdEBrb25naHEuY29tIiwiY2xpZW50SG9zdCI6Ijg4LjE3NS45LjE0NiIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1jb250YWN0QGtvbmdocS5jb20iLCJjbGllbnRBZGRyZXNzIjoiODguMTc1LjkuMTQ2In0.AE3wHHhElQWnuDCJO_XYSvBw7RND4ZB8FpgB9wKlSR5Zbr3XyFwTrbtOdC5A6DAkMdcZ5s-sWg1qDVefM6k2qVe-gj2kmFcMBBt8DQPD7YBKbHdJGaPxqCDrNOrmhMt6MC7EldHd0rJ4beF7i49q4eCyYuSLCpKeS-eTFw5L-s98uGoRxgEZEocaZl9Atu_ajB84HQBpQ31Z0ObKwrMME7TU4nyOWXFYs7ZcGlhamjC2dmDiVJkxKL3ochq6jbnfQAkwjq6EVrK_0KPdMOANcwoYi0gg2TqDq0b16CSA8zUYYf0qVxV69Dyl_3tcWnqvHs7kzelQMeNMziOl4_GZMg'
Response
: expected value ofx-custom-jwt
plugin:- Base64 encoded:
eyJqa3UiOiJodHRwczovL2tvbmctZ2F0ZXdheTo4NDQzL3gtY3VzdG9tLWp3dC9qd2tzIiwiYWxnIjoiUlMyNTYiLCJ0eXAiOiJKV1QiLCJraWQiOiJrb25nIn0.eyJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsib2ZmbGluZV9hY2Nlc3MiLCJkZWZhdWx0LXJvbGVzLWplcm9tZSIsInVtYV9hdXRob3JpemF0aW9uIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJvcGVuaWQgZW1haWwgcHJvZmlsZSIsInN1YiI6ImNjMTYzZmY1LWJmYzEtNGQ2Zi1hMWMxLTZiMDNlMjk1ZjYyZiIsImF1ZCI6Imh0dHA6Ly9odHRwYmluLmFwaW0uZXUvYW55dGhpbmciLCJpYXQiOjE3MTI2ODU0MTYsImF6cCI6ImNvbnRhY3RAa29uZ2hxLmNvbSIsImNsaWVudEhvc3QiOiI4OC4xNzUuOS4xNDYiLCJjbGllbnRfaWQiOiJjb250YWN0QGtvbmdocS5jb20iLCJhY3IiOiIxIiwiYWN0Ijp7ImNsaWVudF9pZCI6ImNvbnRhY3RAa29uZ2hxLmNvbS1JRDEifSwicHJlZmVycmVkX3VzZXJuYW1lIjoic2VydmljZS1hY2NvdW50LWNvbnRhY3RAa29uZ2hxLmNvbSIsImNsaWVudEFkZHJlc3MiOiI4OC4xNzUuOS4xNDYiLCJpc3MiOiJodHRwczovL2tvbmctZ2F0ZXdheTo4NDQzL3gtY3VzdG9tLWp3dCIsImp0aSI6ImUyYTRhODBkLWNmOTgtNDU1Zi04YTZhLTQwY2Q2MTIzZGU1NSIsImNsaWVudElkIjoiY29udGFjdEBrb25naHEuY29tIiwiZXhwIjoxNzEyNjg3MjE2LCJ0eXAiOiJCZWFyZXIiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2V9.XnTIxPJ59YzpcQ8_Q-N_oyxQYwfJ-NQWY7iOXWmwehhyEotw0Z54r7n3zdm8GQXDHiqTkZFxtD7PnAwtphSRCWNnLe2lR0EV6nDMtElcHPViO-bs_ItFMKbLkKrzkWWNaUcK_oRAOMDB19QBvEaM0eoG6dE2o2a4KxQmtRxxDnB3RcTm5AABd7U9Iuqc6xrBoWBpWYdZakomWnJM78USvCB8sHAk4O2LdvzEbYElFuh-Z04sWE7OQVs_S4IF1xRTwaaeDKI2M3MVVvpIVaY-yUJ7tfXvxau4fc6fTEOGjqbv_oKqrKHHR8gcHJHsFppiKPpe2cn_S5xmiirHViBlaw
- JSON decoded:
{ "header": { "typ": "JWT", "alg": "RS256", "kid": "kong", "jku": "https://kong-gateway:8443/x-custom-jwt/jwks" }, "payload": { "realm_access": { "roles": [ "offline_access", "default-roles-jerome", "uma_authorization" ] }, "resource_access": { "account": { "roles": [ "manage-account", "manage-account-links", "view-profile" ] } }, "scope": "openid email profile", "sub": "cc163ff5-bfc1-4d6f-a1c1-6b03e295f62f", "aud": "http://httpbin.apim.eu/anything", "iat": 1712685416, "azp": "[email protected]", "clientHost": "88.175.9.146", "client_id": "[email protected]", "acr": "1", "act": { "client_id": "[email protected]" }, "preferred_username": "[email protected]", "clientAddress": "88.175.9.146", "iss": "https://kong-gateway:8443/x-custom-jwt", "jti": "e2a4a80d-cf98-455f-8a6a-40cd6123de55", "clientId": "[email protected]", "exp": 1712687216, "typ": "Bearer", "email_verified": false }, "signature": "xxxxx" }
- Open the
httpbin
Service - Create a new Route:
- name=
basicAuth
- path=
/basicAuth
- Add
Basic Authentication
plugin to the Route (Leave default parameters) - Open the
[email protected]
consumer, go on Credentials / Basic Authentication, click on a+ New Basic Auth Credential
and put:
- username=
my-user
- password=
My p@ssword!
- Click on save
- Test
-
Request
:http -a 'my-user:My p@ssword!' kong-gateway:8000/basicAuth
If you imported the configuration with decK you'll have a 401 error. It's due to hashing of passwords, so you have to redefine it: see step #4
-
Response
: expected value ofx-custom-jwt
plugin:- Base64 encoded:
eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8va29uZy1nYXRld2F5Ojg0NDMveC1jdXN0b20tand0L2p3a3MiLCJ0eXAiOiJKV1QiLCJraWQiOiJrb25nIn0.eyJjbGllbnRfaWQiOiJteS11c2VyIiwiaWF0IjoxNzEyNzY3MTM2LCJhY3QiOnsiY2xpZW50X2lkIjoiY29udGFjdEBrb25naHEuY29tLUlEMSJ9LCJqdGkiOiI5MjAyNjhmOC05MzFlLTRkMjYtODUyMi1jNmJhMGJhMjkzY2IiLCJleHAiOjE3MTI3Njg5MzYsImlzcyI6Imh0dHBzOi8va29uZy1nYXRld2F5Ojg0NDMveC1jdXN0b20tand0IiwiYXVkIjoiaHR0cDovL2h0dHBiaW4uYXBpbS5ldS9hbnl0aGluZyJ9.N0g0hkUCbFuaccJS32TQJI02wIbMwC1Qj8UnaVbYahokfCulGZkPP9rwmSy73PYJM2vab6PLoqeKQ7XqUIUtIMSuvNS4W6fcEO1ilVt_2LQyqYFR3NDIRLjVf3_LyGWcExsxceon-8LGfrZN817GlLG5XbHzIXZXPDsdiAca_nnZgFaWK7BChF4IOpym7clHD4c6Uh0XDLEkgLzinZLRGm-PTy4REKq7yF3V913aMrS-gMaSDJbpGk6TWGEKERKoyGxvN8y2vH0y-6TA-XVWUM8U3Vdg-wjczlEbMXmlvFdVc2hRAsrjgb19vph4LH2NkvauZdBsP7UBXhz2dKWNkw
- JSON decoded:
{ "header": { "typ": "JWT", "alg": "RS256", "kid": "kong", "jku": "https://kong-gateway:8443/x-custom-jwt/jwks" }, "payload": { "client_id": "my-user", "iat": 1712767136, "act": { "client_id": "[email protected]" }, "jti": "920268f8-931e-4d26-8522-c6ba0ba293cb", "exp": 1712768936, "iss": "https://kong-gateway:8443/x-custom-jwt", "aud": "http://httpbin.apim.eu/anything" }, "signature": "xxxxx" }
- Create a CA Certificate: open Certificates page and click
New CA Certficate
- Copy/paste the content of
./mTLS/ca.cert.pem
in the CA field - Click on Create
- Open the CA Certificate just created and copy/paste its
ID
- Open the
httpbin
Service - Create a new a Route:
- name=
mtlsAuth
- path=
/mtlsAuth
- Add
Mutual TLS Authentication
plugin to the Route and add to config.ca_certificates theÌD
of the CA cert - Click on Install
- Check the presence of
[email protected]
consumer (linked with the client certificate) - Click on create
- Test
-
Request
:http --verify=no --cert=./mTLS/01.pem --cert-key=./mTLS/client.key https://localhost:8443/mtlsAuth
-
Response
: expected value ofx-custom-jwt
plugin:- Base64 encoded:
eyJqa3UiOiJodHRwczovL2tvbmctZ2F0ZXdheTo4NDQzL3gtY3VzdG9tLWp3dC9qd2tzIiwiYWxnIjoiUlMyNTYiLCJ0eXAiOiJKV1QiLCJraWQiOiJrb25nIn0.eyJhY3QiOnsiY2xpZW50X2lkIjoiY29udGFjdEBrb25naHEuY29tLUlEMSJ9LCJqdGkiOiIzOGNhMGY5MS1mZGJmLTQ5OTMtOWE4Ni0yZDUwYWFiMWY3MGIiLCJpc3MiOiJodHRwczovL2tvbmctZ2F0ZXdheTo4NDQzL3gtY3VzdG9tLWp3dCIsImNsaWVudF9pZCI6IkM9VVMsIFNUPUNhbGlmb3JuaWEsIE89S29uZyBJbmMuLCBPVT1Lb25uZWN0LCBDTj1rb25nLCBlbWFpbEFkZHJlc3M9Y29udGFjdEBrb25naHEuY29tIiwiZXhwIjoxNzEyNjg3MjkzLCJpYXQiOjE3MTI2ODU0OTMsImF1ZCI6Imh0dHA6Ly9odHRwYmluLmFwaW0uZXUvYW55dGhpbmcifQ.EzLDNu05GJRlYptJlAMDppxGw_bsIsQDZTpqeATvlacDWKilQyx2wSIwwdzzuouCoJw2odl_bSN-8Le6AhVrP3Ao0Ak3XL9LTrIbuKbJa3EYZGelOit7KcT51VpJfRWewXOPgKuDY7zPCw9oHisnV-gCZKR_IfY3-7oj4ILHczIpBt6JB8D2FstCGYwTLI5CV9JWlIU1QZpCQMord48tAvcyjYRL3-1qafds9H6Ko-w6UUlIPR5YB0pKstctVzb1-zotE4rbVqYK0zorPlBEobxOBNlZ3ATqwckx-onOvvN6IZEy6J3yqWLFI9K0iSymvgaWu2w2ERIZCJCAqPNIwQ
- JSON decoded:
{ "header": { "typ": "JWT", "alg": "RS256", "kid": "kong", "jku": "https://kong-gateway:8443/x-custom-jwt/jwks" }, "payload": { "act": { "client_id": "[email protected]" }, "jti": "38ca0f91-fdbf-4993-9a86-2d50aab1f70b", "iss": "https://kong-gateway:8443/x-custom-jwt", "client_id": "C=US, ST=California, O=Kong Inc., OU=Konnect, CN=kong, [email protected]", "exp": 1712687293, "iat": 1712685493, "aud": "http://httpbin.apim.eu/anything" }, "signature": "xxxxx" }
- Open the
httpbin
Service - Create a new a Route:
- name=
apiKey
- path=
/apiKey
- Add
Key Auth
plugin to the Route with:
- config.key_names=
apikey
- Open the
[email protected]
consumer, click on Credentials / Key Authentication and click on+ New Key Auth Credential
, put:
- Key=
012345AZERTY!
- Click on
Create
- Test
-
Request
:http kong-gateway:8000/apiKey apikey:'012345AZERTY!'
-
Response
: expected value ofx-custom-jwt
plugin:- Base64 encoded:
eyJraWQiOiJrb25nIiwidHlwIjoiSldUIiwiYWxnIjoiUlMyNTYiLCJqa3UiOiJodHRwczovL2tvbmctZ2F0ZXdheTo4NDQzL3gtY3VzdG9tLWp3dC9qd2tzIn0.eyJhY3QiOnsiY2xpZW50X2lkIjoiY29udGFjdEBrb25naHEuY29tLUlEMSJ9LCJqdGkiOiJkNzMxNWU5YS1hYjJjLTRlOGMtYWRhOS0zYjEyYzNiNGYzZTQiLCJpc3MiOiJodHRwczovL2tvbmctZ2F0ZXdheTo4NDQzL3gtY3VzdG9tLWp3dCIsImF1ZCI6Imh0dHA6Ly9odHRwYmluLmFwaW0uZXUvYW55dGhpbmciLCJpYXQiOjE3MTI1OTY0OTMsImV4cCI6MTcxMjU5ODI5MywiY2xpZW50X2lkIjoiMDEyMzQ1QVpFUlRZISJ9.GxM-20uKCkkN06IVSLAyR97QsR2mpMXnaIZzvyuD_cQo5ETIw6Axkb0X8rmNtPONa27okdPB_xVV8XOHC2QSeF4p8h7LZzgZKUg1_7Ixjw4A0Xs5CrRk58aSxFP1EjBGGR7jL896sqtTjz2coJZ7q0ZTqcTG0VDvMCoxmVYa4G5XDm-zOABkFf-Cp4oWxMkFxF3b6m22rjQeI25_5NxJaNAJM6VFVBcmXF9wJTDiOie11eKScuYNRgoICp_XDgPpqLWET4DIPYYWCw_ZFG9vlckXBteTVdEZxvxLVvVtxcrANeDRN3RR0XcSByh5pOIa-2rsa7cUGEyGDVeS4pwIIQ
- JSON decoded:
{ "header": { "typ": "JWT", "alg": "RS256", "kid": "kong", "jku": "https://kong-gateway:8443/x-custom-jwt/jwks" }, "payload": { "act": { "client_id": "[email protected]" }, "jti": "d7315e9a-ab2c-4e8c-ada9-3b12c3b4f3e4", "iss": "https://kong-gateway:8443/x-custom-jwt", "aud": "http://httpbin.apim.eu/anything", "iat": 1712596493, "exp": 1712598293, "client_id": "012345AZERTY!" }, "signature": "xxxxx" }
Use the JWT plugin to check the JWS. The JWT plugin loads the credentials from the Consumer entity depending of algorithm:
- For RSA (RS256, RS512): the Consumer credential uses the PEM format (derived from the Public JWK)
- For HMAC (SH256, SH512): the Consumer credential uses the Symetric Key (see
k
value in the JWK)
Let's create an /introspect
Route with the JWT plugin and Request Termination plugin:
- Get the Credential
- For RSA:
- Get the content of RS256-public.pem or
- Build a PEM file from JWK public key by using the convert-RSA-JWK-to-PEM.js node.js tool:
node convert-RSA-JWK-to-PEM.js RS256-jwk-public.json
- Put the content in a
.pem
file by including-----BEGIN PUBLIC KEY-----
and-----END PUBLIC KEY-----
- For HMAC:
- Get the
k
content of SH256-jwk-symetric-key.json
- Get the
- Attach the JWT credential to the Consumer
- For Kong EE: use Kong Manager:
- Open the
[email protected]
consumer, click on Credentials / JWT and click on+ New JWT Credential
, put:- For RSA (RS256):
- algorithm=
RS256
- key=
my-user
- rsa_public_key=copy/paste the PEM content from step #1 (RS256-public.pem)
- click on Save
- algorithm=
- For HMAC (SH256):
- algorithm=
SH256
- key=
my-user
- secret=copy/paste the
k
content of SH256-jwk-symetric-key.json - click on Save
- algorithm=
- For RSA (RS256):
- Open the
- For Konnect: use API:
- For RSA (RS256):
http https://us.api.konghq.com/konnect-api/api/runtime_groups/<**YOUR_RUNTIME_GROUP_ID**>/consumers/<**[email protected]_CONSUMER_ID**>/jwt Authorization:'Bearer kpat_<**YOUR_PAT**>' algorithm=RS256 key=my-user [email protected]
- For HMAC (SH256):
http https://us.api.konghq.com/konnect-api/api/runtime_groups/<**YOUR_RUNTIME_GROUP_ID**>/consumers/<**[email protected]_CONSUMER_ID**>/jwt Authorization:'Bearer kpat_<**YOUR_PAT**>' algorithm=HS256 key=my-user secret="<**SECRET_FROM_STEP_ABOVE**>"
- For RSA (RS256):
- Create the
/introspect
Route
- Create a new a Route (with no Gateway Service)
- name=
Introspect
- path=
/introspect
- click on Save
- name=
- Add the
JWT
plugin to the Route with:- config.claims_to_verify=
exp
- config.key_claim_name=
client_id
- click on Save
- config.claims_to_verify=
- Add the
Request Termination
plugin to the Route with:- config.status_code=
200
- config.body=
{"message": "Ok"}
- config.content_type=
application/json
- config.status_code=
- Get a new
x-custom-jwt
with the Basic Authorization
Request
:http -a 'my-user:My p@ssword!' kong-gateway:8000/basicAuth
Response
: copy/paste thex-custom-jwt
value- Base64 encoded:
eyJ0eXAiOiJKV1QiLCJqa3UiOiJodHRwczovL2tvbmctZ2F0ZXdheTo4NDQzL3gtY3VzdG9tLWp3dC9qd2tzIiwia2lkIjoia29uZyIsImFsZyI6IlJTMjU2In0.eyJjbGllbnRfaWQiOiJteS11c2VyIiwiaXNzIjoiaHR0cHM6Ly9rb25nLWdhdGV3YXk6ODQ0My94LWN1c3RvbS1qd3QiLCJhdWQiOiJodHRwOi8vaHR0cGJpbi5hcGltLmV1L2FueXRoaW5nIiwiaWF0IjoxNzEzMzc0MTU2LCJqdGkiOiIwYjFjNTdmNy01NjBlLTRjYmYtODAzMi1lMTdlMGU4MjIzYWYiLCJleHAiOjE3MTMzNzU5NTYsImFjdCI6eyJjbGllbnRfaWQiOiJjb250YWN0QGtvbmdocS5jb20tSUQxIn19.UoaBk5PPl2GkwKOGClD0k7bfjpvp_7e9-n4oNhRZHyv-Om1w2qriTN-ehCBlOIrECY4drFgsU3NE0EGelmhwmRcKz3K9u79fL0R1IgfZ2sI_sEh2SVau7lquWFFV-B-WWe1uY53CVBOF1XJBmUCEHxDLBr1zCVA-izXq7Xfr9-ls3Hb1j2B09GdhXSXroxIniu4pcWZHN4GP-nl5rmxo1eASvs5JJo7HD3G4ivczEy_ujtEtVUGQ23zBLdEzUxr9p91gjTeao9l7uQwhCjSuLEXcfHuU8A2OcgIS1r3rbpRSoyCEcSCg7al2FjsGeaGUzvXGe4xssTOGBwSVXtxApw
- Base64 encoded:
- Check the
x-custom-jwt
with/introspect
route
Request
:http kong-gateway:8000/introspect Authorization:'Bearer eyJ0eXAiOiJKV1QiLCJqa3UiOiJodHRwczovL2tvbmctZ2F0ZXdheTo4NDQzL3gtY3VzdG9tLWp3dC9qd2tzIiwia2lkIjoia29uZyIsImFsZyI6IlJTMjU2In0.eyJjbGllbnRfaWQiOiJteS11c2VyIiwiaXNzIjoiaHR0cHM6Ly9rb25nLWdhdGV3YXk6ODQ0My94LWN1c3RvbS1qd3QiLCJhdWQiOiJodHRwOi8vaHR0cGJpbi5hcGltLmV1L2FueXRoaW5nIiwiaWF0IjoxNzEzMzc0MTU2LCJqdGkiOiIwYjFjNTdmNy01NjBlLTRjYmYtODAzMi1lMTdlMGU4MjIzYWYiLCJleHAiOjE3MTMzNzU5NTYsImFjdCI6eyJjbGllbnRfaWQiOiJjb250YWN0QGtvbmdocS5jb20tSUQxIn19.UoaBk5PPl2GkwKOGClD0k7bfjpvp_7e9-n4oNhRZHyv-Om1w2qriTN-ehCBlOIrECY4drFgsU3NE0EGelmhwmRcKz3K9u79fL0R1IgfZ2sI_sEh2SVau7lquWFFV-B-WWe1uY53CVBOF1XJBmUCEHxDLBr1zCVA-izXq7Xfr9-ls3Hb1j2B09GdhXSXroxIniu4pcWZHN4GP-nl5rmxo1eASvs5JJo7HD3G4ivczEy_ujtEtVUGQ23zBLdEzUxr9p91gjTeao9l7uQwhCjSuLEXcfHuU8A2OcgIS1r3rbpRSoyCEcSCg7al2FjsGeaGUzvXGe4xssTOGBwSVXtxApw'
Response
:- The JWT is valid:
"HTTP/1.1 200 OK" { "message": "Ok" }
- The JWT has an Invalid signature:
"HTTP/1.1 401 Unauthorized" { "message": "Invalid signature" }
- The JWT has expired:
"HTTP/1.1 401 Unauthorized" { "exp": "token expired" }
- The JWT does not have a common
client_id
value in the Consumer JWT credentials and inx-custom-jwt
:"HTTP/1.1 401 Unauthorized" { "message": "No credentials found for given 'client_id'" }
- The JWT is valid:
Check the JWS with https://jwt.io
- Open https://jwt.io
- Copy/paste the
x-custom-jwt
header value
- If everything works correctly the jwt.io sends a
Signature Verified
message - The public key is downloaded automatically through the
x-custom-jwt-jwks
route and theRequest Termination
plugin. If it's not the case, open the Browser Developer Tools and see the network tab and console tab. The classic issue is getting the JWKS by using the self-signed Kong certificate (on 8443); it's easy to fix the issue by opening a new tab, going on thejku
request (i.e. https://kong-gateway:8443/x-custom-jwt/jwks), clicking on Advanced and by clicking onProceed to
- There is a known limitation on jwt.io with
"use": "enc"
the match isn't done correctly and the JWK is not loaded automatically: we simply have to copy/paste the public JWK directly in theVERIFY SIGNATURE
of the web page. With"use": "sig"
there is no restriction