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 client web_client and a user zookabazooka with password password. 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, click Next. Set Client authentication to OFF, enable Standard flow and Direct access grants under Authentication flow, click Next. In Valid redirect URIs, add *, in Web origins, add *, click Save.
  • Create a user, in the Credentials tab, set the password and disable the Temporary option.
  • Go to http://localhost:8080/realms/{realmName}/protocol/openid-connect/certs and copy the second key (the one with RSA256 as alg). Convert that key to PEM format and paste it into the rsa_public_key section of the kong.yml file. Also, in that yaml, update the key name to match your realm URL key: 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.ymlto 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


Comments: