Kong API Gateway + Keycloak
Introduction
It’s a showcase of Keycloak and Kong API Gateway (with JWT Plugin), and how they interact with a protected endpoint.
NOTE: For the purpose of this demo, I’m not showing the full Keycloak configuration. Instead I have added the realm configuration to import it from Keycloak. It contains the realm
kong_keycloak_api
, with a clientweb_client
and a userzookabazooka
with passwordpassword
. The realm also contains the JWKS to generate the PEM file. It’s running in development mode (with H2 as database). I’m also using Kong Gateway in DB-less mode (with declarative configuration).
First, create the Kong declarative configuration inside a folder named kong
, we should call the file kong.yml
# ./kong/kong.yml
_format_version: "3.0"
_transform: true
services:
- host: httpbin.konghq.com
name: example_service
port: 80
protocol: http
routes:
- name: example_route
methods:
- GET
paths:
- /mock
strip_path: true
Then, we can run the Kong DB-less container:
docker run --rm --name kong-dbless \
--network=kong-keycloak \
-v "$(pwd)/kong/:/kong/declarative/" \
-e "KONG_DATABASE=off" \
-e "KONG_DECLARATIVE_CONFIG=/kong/declarative/kong.yml" \
-e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
-e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
-e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
-e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
-e KONG_LICENSE_DATA \
-p 8000:8000 \
kong/kong-gateway:3.4
Next, we test the endpoint by openning the terminal and running:
curl localhost:8000/mock/anything
Great! Now, let’s add Keycloak. But first, we’ll use Docker Compose instead of Docker commands. Create a docker-compose.yaml file in the root folder:
# ./docker-compose.yaml
version: "3.9"
services:
kong:
container_name: kong-dbless
image: kong/kong-gateway:3.4
volumes:
- ./kong/:/kong/declarative/
environment:
- KONG_DATABASE=off
- KONG_DECLARATIVE_CONFIG=/kong/declarative/kong.yml
- KONG_PROXY_ACCESS_LOG=/dev/stdout
- KONG_ADMIN_ACCESS_LOG=/dev/stdout
- KONG_PROXY_ERROR_LOG=/dev/stderr
- KONG_ADMIN_ERROR_LOG=/dev/stderr
- KONG_LICENSE_DATA
ports:
- "8000:8000"
networks:
- kong-keycloak
networks:
kong-keycloak:
Test if it’s working:
docker-compose up
Keycloak
NOTE: If you want to configure the realm yourself, follow these steps:
- Create a realm.
- Inside that realm, go to
Manage
->Client
->Clients list
->Create client
.- Give a name to the
Client ID
, clickNext
. SetClient authentication
to OFF, enableStandard flow
andDirect access grants
underAuthentication flow
, clickNext
. InValid redirect URIs
, add*
, inWeb origins
, add*
, clickSave
.- Create a user, in the
Credentials
tab, set the password and disable theTemporary
option.- Go to
http://localhost:8080/realms/{realmName}/protocol/openid-connect/certs
and copy the second key (the one withRSA256
asalg
). Convert that key to PEM format and paste it into thersa_public_key
section of thekong.yml
file. Also, in that yaml, update the key name to match your realm URLkey: http://localhost:8080/realms/{realmName}
Create a keycloak/
folder and copy the content from https://raw.githubusercontent.com/hexdump95/kong_keycloak_api/refs/heads/main/keycloak/kong_keycloak_api-realm.json
Now, let’s add Keycloak. Update the docker-compose.yaml
:
# ./docker-compose.yaml
version: "3.9"
services:
kong:
container_name: kong-dbless
image: kong/kong-gateway:3.4
volumes:
- ./kong/:/kong/declarative/
environment:
- KONG_DATABASE=off
- KONG_DECLARATIVE_CONFIG=/kong/declarative/kong.yml
- KONG_PROXY_ACCESS_LOG=/dev/stdout
- KONG_ADMIN_ACCESS_LOG=/dev/stdout
- KONG_PROXY_ERROR_LOG=/dev/stderr
- KONG_ADMIN_ERROR_LOG=/dev/stderr
- KONG_LICENSE_DATA
ports:
- "8000:8000"
networks:
- kong-keycloak
keycloak:
container_name: keycloak
image: quay.io/keycloak/keycloak:26.0
volumes:
- ./keycloak/:/home/keycloak/realms/
entrypoint: ["/bin/bash"]
command: |
-c
"/opt/keycloak/bin/kc.sh import --dir /home/keycloak/realms/ &&
/opt/keycloak/bin/kc.sh start-dev"
environment:
- KC_BOOTSTRAP_ADMIN_USERNAME=admin
- KC_BOOTSTRAP_ADMIN_PASSWORD=secret
ports:
- "8080:8080"
networks:
- kong-keycloak
networks:
kong-keycloak:
Update kong/kong.yml
to add the plugin to the example_service
and add an auth consumer with the kong_keycloak_api
RSA256 PEM:
# ./kong/kong.yml
_format_version: "3.0"
_transform: true
services:
- host: httpbin.konghq.com
name: example_service
port: 80
protocol: http
routes:
- name: example_route
methods:
- GET
paths:
- /mock
strip_path: true
plugins:
- name: jwt
protocols:
- http
- https
config:
claims_to_verify:
- exp
uri_param_names:
- jwt
consumers:
- username: auth_consumer
jwt_secrets:
- algorithm: RS256
key: http://localhost:8080/realms/kong_keycloak_api
secret: randomSecret
rsa_public_key: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jQsiIg9PjVVzhj/ZMRa
qB7+zFbFMGfvE537yemH8Vw1BWK2SrFPvZggRHYaE6hYAGyLCmesOa8jKINZbw2a
Pq09obPyMPR5icMA0c8ikrXkh0rQXCWu+vOcrJZ8XOH90QUc0HtQuiOqOhSHebCO
S/f8E7oR4XyFambXvwTxaH6rc2LIzZTFmCgrswMZXV23vHfrbfv9WZf9Otf6mJMe
Sgx/inI7eHM73k2AFzVRwGaMlRGbI3QfB5Yciq5vTN9rqWOwpLjEllP2xQAmErXY
rMCfw35uxeiBMraARmFy/T0LbWJ20xr+FcEHjaD5AglamznVAS4qp2H/nSUPtcAR
ewIDAQAB
-----END PUBLIC KEY-----
Run docker-compose again
Check if we can access to localhost:8000/mock/anything
:
curl localhost:8000/mock/anything
We’ll get an authorization error because now we need a JWT to access the endpoint.
To get the token, use the Keycloak login endpoint:
curl -X POST 'http://localhost:8080/realms/kong_keycloak_api/protocol/openid-connect/token' \
-d 'grant_type=password' \
-d 'username=zookabazooka' \
-d 'password=password' \
-d 'client_id=web_client'
Copy the access_token string and try accessing localhost:8000/mock/anything
again, but this time pass the token in the Authorization header:
curl 'localhost:8000/mock/anything' \
-H 'Authorization: Bearer {accessToken}'
Now that it’s working, let’s add the login endpoint to Kong, edit the kong/kong.yml again:
#./kong/kong.yml
_format_version: "3.0"
_transform: true
services:
- host: httpbin.konghq.com
name: example_service
port: 80
protocol: http
routes:
- name: example_route
methods:
- GET
paths:
- /mock
strip_path: true
plugins:
- name: jwt
protocols:
- http
- https
config:
claims_to_verify:
- exp
uri_param_names:
- jwt
- host: keycloak
name: login_service
port: 8080
protocol: http
path: /realms/kong_keycloak_api/protocol/openid-connect/token
routes:
- name: login_route
methods:
- POST
paths:
- /login
strip_path: true
consumers:
- username: auth_consumer
jwt_secrets:
- algorithm: RS256
key: http://localhost:8080/realms/kong_keycloak_api
secret: randomSecret
rsa_public_key: |
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jQsiIg9PjVVzhj/ZMRa
qB7+zFbFMGfvE537yemH8Vw1BWK2SrFPvZggRHYaE6hYAGyLCmesOa8jKINZbw2a
Pq09obPyMPR5icMA0c8ikrXkh0rQXCWu+vOcrJZ8XOH90QUc0HtQuiOqOhSHebCO
S/f8E7oR4XyFambXvwTxaH6rc2LIzZTFmCgrswMZXV23vHfrbfv9WZf9Otf6mJMe
Sgx/inI7eHM73k2AFzVRwGaMlRGbI3QfB5Yciq5vTN9rqWOwpLjEllP2xQAmErXY
rMCfw35uxeiBMraARmFy/T0LbWJ20xr+FcEHjaD5AglamznVAS4qp2H/nSUPtcAR
ewIDAQAB
-----END PUBLIC KEY-----
Test if it’s working:
curl -X POST http://localhost:8000/login \
-d 'grant_type=password' \
-d 'username=zookabazooka' \
-d 'password=password' \
-d 'client_id=web_client'
Full solution at: https://github.com/hexdump95/kong_keycloak_api