⚠️ This fork is a continuation of https://github.com/telekom-digioss/kong-plugin-jwt-keycloak for a limited set of version combinationsThe official author of the plugin no longer maintains it since 24.08.2021
Details see: https://github.com/gbbirkisson/kong-plugin-jwt-keycloak/blob/master/README.md
A plugin for the Kong Gateway to validate access tokens issued by Keycloak. It uses the Well-Known Uniform Resource Identifiers provided by Keycloak to load JWK public keys from issuers that are specifically allowed for each endpoint.
The biggest advantages of this plugin are that it supports:
- Rotating public keys
- Authorization based on token claims:
scoperealm_accessresource_access
- Matching Keycloak users/clients to Kong consumers
If you have any suggestion or comments, please feel free to open an issue on this GitHub page.
There are a few limitations about testing combinations:
- Due to the nature of the test setup, we can only test a multitude of Kong versions with a limited set of rather recent Keycloak versions.
| Kong Version | Keycloak Version | Passing |
|---|---|---|
| 2.8.3 | 26.0 | ✅ |
| 2.8.3 | 26.3 | ✅ |
| 3.0 | 26.0 | ✅ |
| 3.0 | 26.3 | ✅ |
| 3.4 | 26.0 | ✅ |
| 3.4 | 26.3 | ✅ |
| 3.5 | 26.0 | ✅ |
| 3.5 | 26.3 | ✅ |
| 3.8 | 26.0 | ✅ |
| 3.8 | 26.3 | ✅ |
| 3.9.1 | 26.0 | ✅ |
| 3.9.1 | 26.3 | ✅ |
luarocks install kong-plugin-jwt-keycloakexport PLUGIN_VERSION=1.6.0-1
luarocks make
luarocks pack kong-plugin-jwt-keycloak ${PLUGIN_VERSION}export PLUGIN_VERSION=1.6.0-1
luarocks install jwt-keycloak-${PLUGIN_VERSION}.all.rockSet enabled kong enabled plugins, i.e. with environmental variable: KONG_PLUGINS="bundled,jwt-keycloak"
In some cases you might want to change the execution priority of the plugin. You can do that by setting an environmental
variable: JWT_KEYCLOAK_PRIORITY="900"
See Dockerfile for more concrete examples.
The same principle applies to this plugin as the standard jwt plugin that comes with kong. You can enable it on service, routes and globally.
curl -X POST http://localhost:8001/services/{service}/plugins \
--data "name=jwt-keycloak" \
--data "config.allowed_iss=http://localhost:8080/auth/realms/master"curl -X POST http://localhost:8001/routes/{route_id}/plugins \
--data "name=jwt-keycloak" \
--data "config.allowed_iss=http://localhost:8080/auth/realms/master"curl -X POST http://localhost:8001/plugins \
--data "name=jwt-keycloak" \
--data "config.allowed_iss=http://localhost:8080/auth/realms/master"| Parameter | Requied | Default | Description |
|---|---|---|---|
| name | yes | The name of the plugin to use, in this case keycloak-jwt. |
|
| service_id | semi | The id of the Service which this plugin will target. | |
| route_id | semi | The id of the Route which this plugin will target. | |
| enabled | no | true |
Whether this plugin will be applied. |
| config.uri_param_names | no | jwt |
A list of querystring parameters that Kong will inspect to retrieve JWTs. |
| config.cookie_names | no | A list of cookie names that Kong will inspect to retrieve JWTs. | |
| config.claims_to_verify | no | exp |
A list of registered claims (according to RFC 7519) that Kong can verify as well. Accepted values: exp, nbf. |
| config.anonymous | no | An optional string (consumer uuid) value to use as an “anonymous” consumer if authentication fails. If empty (default), the request will fail with an authentication failure 4xx. Please note that this value must refer to the Consumer id attribute which is internal to Kong, and not its custom_id. |
|
| config.run_on_preflight | no | true |
A boolean value that indicates whether the plugin should run (and try to authenticate) on OPTIONS preflight requests, if set to false then OPTIONS requests will always be allowed. |
| config.header_names | no | authorization |
A list of HTTP header names that Kong will inspect to retrieve JWTs. OPTIONS requests will always be allowed. |
| config.maximum_expiration | no | 0 |
An integer limiting the lifetime of the JWT to maximum_expiration seconds in the future. Any JWT that has a longer lifetime will rejected (HTTP 403). If this value is specified, exp must be specified as well in the claims_to_verify property. The default value of 0 represents an indefinite period. Potential clock skew should be considered when configuring this value. |
| config.algorithm | no | RS256 |
Deprecated - No longer used. The plugin now automatically validates that the JWT algorithm (alg header) is one of the supported algorithms: RS256, RS384, RS512, ES256, ES384, or ES512. This field is kept for backwards compatibility but has no effect. |
| config.allowed_iss | yes | A list of allowed issuers for this route/service/api. Can be specified as a string or as a Pattern. |
|
| config.iss_key_grace_period | no | 10 |
An integer that sets the number of seconds until public keys for an issuer can be updated after writing new keys to the cache. This is a guard so that the Kong cache will not invalidate every time a token signed with an invalid public key is sent to the plugin. |
| config.well_known_template | false | see description | A string template that the well known endpoint for keycloak is created from. String formatting is applied on the template and %s is replaced by the issuer of the token. Default value is %s/.well-known/openid-configuration |
| config.scope | no | A list of scopes the token must have to access the api, i.e. ["email"]. The token only has to have one of the listed scopes to be authorized. |
|
| config.roles | no | A list of roles of current client the token must have to access the api, i.e. ["uma_protection"]. The token only has to have one of the listed roles to be authorized. |
|
| config.realm_roles | no | A list of realm roles (realm_access) the token must have to access the api, i.e. ["offline_access"]. The token only has to have one of the listed roles to be authorized. |
|
| config.client_roles | no | A list of roles of a different client (resource_access) the token must have to access the api, i.e. ["account:manage-account"]. The format for each entry should be <CLIENT_NAME>:<ROLE_NAME>. The token only has to have one of the listed roles to be authorized. |
|
| config.consumer_match | no | false |
A boolean value that indicates if the plugin should find a kong consumer with id/custom_id that equals the consumer_match_claim claim in the access token. |
| config.consumer_match_claim | no | azp |
The claim name in the token that the plugin will try to match the kong id/custom_id against. |
| config.consumer_match_claim_custom_id | no | false |
A boolean value that indicates if the plugin should match the consumer_match_claim claim against the consumers id or custom_id. By default it matches the consumer against the id. |
| config.consumer_match_ignore_not_found | no | false |
A boolean value that indicates if the request should be let through regardless if the plugin is able to match the request to a kong consumer or not. |
Create service and add the plugin to it, and lastly create a route:
curl -X POST http://localhost:8001/services \
--data "name=httpbin-anything" \
--data "url=http://localhost:8093/anything"
curl -X POST http://localhost:8001/services/httpbin-anything/plugins \
--data "name=jwt-keycloak" \
--data "config.allowed_iss=http://localhost:8080/auth/realms/master"
curl -X POST http://localhost:8001/services/httpbin-anything/routes \
--data "paths=/" Then you can call the API:
curl http://localhost:8000/This should give you a 401 unauthorized. But if we call the API with a token:
export CLIENT_ID=<YOUR_CLIENT_ID>
export CLIENT_SECRET=<YOUR_CLIENT_SECRET>
export TOKENS=$(curl -s -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=client_credentials" \
-d "client_id=${CLIENT_ID}" \
-d "client_secret=${CLIENT_SECRET}" \
http://localhost:8080/auth/realms/master/protocol/openid-connect/token)
export ACCESS_TOKEN=$(echo ${TOKENS} | jq -r ".access_token")
curl -H "Authorization: Bearer ${ACCESS_TOKEN}" http://localhost:8000/ \
--data "plugin=working"This should give you the response: plugin=working
To verify token issuers, this plugin needs to be able to access the
<ISSUER_REALM_URL>/.well-known/openid-configuration and <ISSUER_REALM_URL>/protocol/openid-connect/certs endpoints
of keycloak. If you are getting the error { "message": "Unable to get public key for issuer" } it is probably because
for some reason the plugin is unable to access these endpoints.
Requires:
- docker
docker compose up -ddocker compose up testsThis project has adopted the Contributor Covenant in version 2.1 as our code of conduct. Please see the details in our CODE_OF_CONDUCT.md. All contributors must abide by the code of conduct.
By participating in this project, you agree to abide by its Code of Conduct at all times.
This project follows the REUSE standard for software licensing. Each file contains copyright and license information, and license texts can be found in the ./LICENSES folder. For more information visit https://reuse.software/.