/kong-plugin-jwt-keycloak

A plugin to validate access tokens issued by Keycloak

Primary LanguageLuaApache License 2.0Apache-2.0

Kong plugin jwt-keycloak

Attention: I will no longer be maintaining this plugin. Thanks for all the positive feedback and interest in this project. Feel free to fork and keep it alive. Cheers!

A plugin for the Kong Microservice API 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:
    • scope
    • realm_access
    • resource_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.

Table of Contents

Tested and working for

Kong Version Tests passing
0.13.x
0.14.x
1.0.x
1.1.x
1.2.x
1.3.x
1.4.x
1.5.x
2.0.x
2.1.x
2.2.x
2.3.x
Keycloak Version Tests passing
3.X.X
4.X.X
5.X.X
6.X.X
7.X.X
8.X.X
9.X.X
10.X.X
11.X.X
12.X.X

Installation

Using luarocks

luarocks install kong-plugin-jwt-keycloak

From source

Packing the rock

export PLUGIN_VERSION=1.1.0-1
luarocks make
luarocks pack kong-plugin-jwt-keycloak ${PLUGIN_VERSION}

Installing the rock

export PLUGIN_VERSION=1.1.0-1
luarocks install jwt-keycloak-${PLUGIN_VERSION}.all.rock

Enabling plugin

Set enabled kong enabled plugins, i.e. with environmental variable: KONG_PLUGINS="bundled,jwt-keycloak"

Changing plugin priority

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"

Examples

See Dockerfile or luarocks Dockerfile for more concrete examples.

Usage

Enabling on endpoints

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.

Service

curl -X POST http://localhost:8001/services/{service}/plugins \
    --data "name=jwt-keycloak" \
    --data "config.allowed_iss=http://localhost:8080/auth/realms/master"

Route

curl -X POST http://localhost:8001/routes/{route_id}/plugins \
    --data "name=jwt-keycloak" \
    --data "config.allowed_iss=http://localhost:8080/auth/realms/master"

Globally

curl -X POST http://localhost:8001/plugins \
    --data "name=jwt-keycloak" \
    --data "config.allowed_iss=http://localhost:8080/auth/realms/master"

Parameters

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.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 The algorithm used to verify the token’s signature. Can be HS256, HS384, HS512, RS256, or ES256.
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.

Example

Create service and add the plugin to it, and lastly create a route:

curl -X POST http://localhost:8001/services \
    --data "name=mockbin-echo" \
    --data "url=http://mockbin.org/echo"

curl -X POST http://localhost:8001/services/mockbin-echo/plugins \
    --data "name=jwt-keycloak" \
    --data "config.allowed_iss=http://localhost:8080/auth/realms/master"

curl -X POST http://localhost:8001/services/mockbin-echo/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

Caveats

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.

Testing

Requires:

  • make
  • docker

Because testing uses docker host networking it does not work on MacOS

Setup before tests

make keycloak-start

Running tests

make test-unit # Unit tests
make test-integration # Integration tests with postgres
make test-integration KONG_DATABASE=cassandra # Integration tests with cassandra
make test # All test with postgres
make test KONG_DATABASE=cassandra # All test with cassandra
make test-all # All test with cassandra and postgres and multiple versions of kong

Useful debug commands

make kong-log # For proxy logs
make kong-err-proxy # For proxy error logs
make kong-err-admin # For admin error logs