Keycloak + Kong API Gateway: End-to-End Setup

Guilliano Molaire Guilliano Molaire Updated June 8, 2026 8 min read

Last updated: March 2026

Introduction

API gateways sit at the front door of your microservices architecture, handling cross-cutting concerns like authentication, rate limiting, and request routing. When you pair Kong Gateway with Keycloak, you get a powerful combination: Kong handles traffic management while Keycloak manages identity, tokens, and single sign-on.

This guide walks through a complete integration. You will set up both services with Docker Compose, configure Kong’s OIDC plugin to validate Keycloak-issued JWTs, map Kong consumers to Keycloak clients, and apply per-client rate limiting. By the end, every request reaching your upstream services will be authenticated and authorized at the gateway level.

If you are new to Keycloak deployment, our Docker Compose Generator can help you scaffold a local Keycloak instance quickly.

Architecture Overview

The request flow looks like this:

  1. A client application authenticates with Keycloak and receives an access token (JWT).
  2. The client sends API requests to Kong with the token in the Authorization header.
  3. Kong’s OIDC plugin validates the JWT against Keycloak’s public keys.
  4. If valid, Kong maps the token to a consumer and applies rate-limiting and other policies.
  5. Kong forwards the request to the upstream service with identity context in headers.

This architecture offloads all authentication logic from your microservices. Your backend code only needs to trust headers set by Kong.

Prerequisites

  • Docker and Docker Compose installed
  • Basic familiarity with OAuth 2.0 and OpenID Connect
  • A terminal with curl available

Docker Compose Setup

Below is a complete docker-compose.yml that spins up Keycloak, Kong (with its database), and a simple upstream echo service:

version: "3.9"

services:
  # --- Keycloak ---
  keycloak-db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: keycloak_pass
    volumes:
      - keycloak_data:/var/lib/postgresql/data

  keycloak:
    image: quay.io/keycloak/keycloak:26.1.0
    command: start-dev
    environment:
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://keycloak-db:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: keycloak_pass
      KC_HOSTNAME_STRICT: "false"
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
    ports:
      - "8080:8080"
    depends_on:
      - keycloak-db

  # --- Kong ---
  kong-db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: kong
      POSTGRES_USER: kong
      POSTGRES_PASSWORD: kong_pass
    volumes:
      - kong_data:/var/lib/postgresql/data

  kong-migration:
    image: kong:3.9
    command: kong migrations bootstrap
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-db
      KONG_PG_USER: kong
      KONG_PG_PASSWORD: kong_pass
    depends_on:
      - kong-db

  kong:
    image: kong:3.9
    environment:
      KONG_DATABASE: postgres
      KONG_PG_HOST: kong-db
      KONG_PG_USER: kong
      KONG_PG_PASSWORD: kong_pass
      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_ADMIN_LISTEN: "0.0.0.0:8001"
    ports:
      - "8000:8000"   # Proxy
      - "8001:8001"   # Admin API
    depends_on:
      kong-migration:
        condition: service_completed_successfully

  # --- Upstream echo service ---
  echo-service:
    image: hashicorp/http-echo
    command: ["-text", '{"message":"Hello from upstream"}']
    ports:
      - "5678:5678"

volumes:
  keycloak_data:
  kong_data:

Start everything:

docker compose up -d

Wait for Keycloak to be ready at http://localhost:8080 and Kong Admin at http://localhost:8001.

Configuring Keycloak

Create a Realm and Client

  1. Log in to the Keycloak admin console at http://localhost:8080/admin with admin / admin.
  2. Create a new realm called api-gateway.
  3. Under Clients, create a new client:
Setting Value
Client ID kong-api
Client Protocol openid-connect
Client Authentication ON
Authorization OFF
Valid Redirect URIs * (for development only)
Web Origins *
  1. On the Credentials tab, note the client secret.

Create Roles and a Test User

Create two realm roles: api-read and api-write.

Then create a test user:

  • Username: testuser
  • Email: [email protected]
  • Set a password (disable “Temporary”)
  • Under Role Mappings, assign api-read

For production deployments, you would configure RBAC policies to control which roles grant access to which API endpoints.

Verify Token Issuance

Obtain a token using the Resource Owner Password Grant (suitable for testing only):

KC_TOKEN=$(curl -s -X POST 
  "http://localhost:8080/realms/api-gateway/protocol/openid-connect/token" 
  -H "Content-Type: application/x-www-form-urlencoded" 
  -d "grant_type=password" 
  -d "client_id=kong-api" 
  -d "client_secret=YOUR_CLIENT_SECRET" 
  -d "username=testuser" 
  -d "password=testpassword" 
  -d "scope=openid" | jq -r '.access_token')

echo $KC_TOKEN

Paste the token into the JWT Token Analyzer to inspect its claims, roles, and expiration.

Configuring Kong

Register the Upstream Service and Route

# Create a service pointing to the echo backend
curl -s -X POST http://localhost:8001/services 
  -d name=echo-service 
  -d url=http://echo-service:5678

# Create a route
curl -s -X POST http://localhost:8001/services/echo-service/routes 
  -d "name=echo-route" 
  -d "paths[]=/api"

Test that the route works without authentication:

curl -s http://localhost:8000/api
# Should return: {"message":"Hello from upstream"}

Enable JWT Plugin

Kong’s built-in JWT plugin validates tokens without needing the full OIDC plugin (which is an Enterprise feature). For open-source Kong, the JWT plugin works well with Keycloak:

# Enable the JWT plugin on the service
curl -s -X POST http://localhost:8001/services/echo-service/plugins 
  -d "name=jwt" 
  -d "config.uri_param_names=jwt" 
  -d "config.claims_to_verify=exp" 
  -d "config.key_claim_name=iss" 
  -d "config.secret_is_base64=false"

Create a Kong Consumer and Map to Keycloak

Kong consumers represent authenticated entities. We create a consumer and associate Keycloak’s JWT signing key:

# Create a consumer
curl -s -X POST http://localhost:8001/consumers 
  -d "username=keycloak-issuer"

# Fetch the Keycloak realm's public key
KC_PUBLIC_KEY=$(curl -s 
  "http://localhost:8080/realms/api-gateway/protocol/openid-connect/certs" 
  | jq -r '.keys[] | select(.use=="sig" and .alg=="RS256") | .x5c[0]')

# Register the JWT credential
# The 'key' must match the 'iss' claim in the token
curl -s -X POST http://localhost:8001/consumers/keycloak-issuer/jwt 
  -d "key=http://localhost:8080/realms/api-gateway" 
  -d "algorithm=RS256" 
  -d "rsa_public_key=-----BEGIN PUBLIC KEY-----
${KC_PUBLIC_KEY}
-----END PUBLIC KEY-----"

Now test with a valid Keycloak token:

# This should succeed
curl -s -H "Authorization: Bearer ${KC_TOKEN}" 
  http://localhost:8000/api

# This should return 401
curl -s http://localhost:8000/api

Alternative: OIDC Plugin (Kong Enterprise / Kong Gateway)

If you have Kong Enterprise or Kong Gateway with the OIDC plugin, the setup is simpler because it handles discovery and key rotation automatically:

curl -s -X POST http://localhost:8001/services/echo-service/plugins 
  -d "name=openid-connect" 
  -d "config.issuer=http://keycloak:8080/realms/api-gateway" 
  -d "config.client_id=kong-api" 
  -d "config.client_secret=YOUR_CLIENT_SECRET" 
  -d "config.auth_methods[]=bearer" 
  -d "config.bearer_token_param_type[]=header" 
  -d "config.scopes_required[]=openid" 
  -d "config.consumer_claim[]=sub" 
  -d "config.consumer_by[]=username"

The OIDC plugin automatically fetches Keycloak’s JWKS endpoint and handles key rotation, which is a significant operational advantage.

Rate Limiting Per Client

One of the biggest benefits of the gateway pattern is applying per-client policies. With the JWT validated and a consumer identified, you can apply rate limiting:

# Global rate limit: 100 requests/minute
curl -s -X POST http://localhost:8001/services/echo-service/plugins 
  -d "name=rate-limiting" 
  -d "config.minute=100" 
  -d "config.policy=local" 
  -d "config.limit_by=consumer"

# Override for a specific high-volume consumer
curl -s -X POST http://localhost:8001/consumers/keycloak-issuer/plugins 
  -d "name=rate-limiting" 
  -d "config.minute=1000" 
  -d "config.policy=local"

The limit_by=consumer setting ensures each Keycloak client gets its own rate limit bucket.

Forwarding Identity Context to Upstream

By default, Kong’s JWT plugin sets the X-Consumer-Username and X-Consumer-ID headers on the upstream request. To pass additional token claims (like roles or email), add the post-function plugin or use request-transformer:

curl -s -X POST http://localhost:8001/services/echo-service/plugins 
  -d "name=request-transformer" 
  -d "config.add.headers[]=X-Auth-Token-Sub:$(kong.ctx.shared.authenticated_jwt_token_claims.sub)" 
  -d "config.add.headers[]=X-Auth-Roles:$(kong.ctx.shared.authenticated_jwt_token_claims.realm_access.roles)"

Alternatively, use the pre-function plugin for custom Lua logic:

-- Custom header injection plugin (serverless function)
local jwt_claims = kong.ctx.shared.authenticated_jwt_token_claims

if jwt_claims then
  kong.service.request.set_header("X-User-Sub", jwt_claims.sub)
  kong.service.request.set_header("X-User-Email", jwt_claims.email or "")

  -- Forward realm roles as comma-separated list
  local roles = jwt_claims.realm_access and jwt_claims.realm_access.roles
  if roles then
    kong.service.request.set_header("X-User-Roles", table.concat(roles, ","))
  end
end

Your upstream microservices can then use these headers for authorization decisions without decoding the JWT themselves.

Testing the Full Flow

Here is a complete test script:

#!/bin/bash
set -e

KEYCLOAK_URL="http://localhost:8080"
KONG_URL="http://localhost:8000"
REALM="api-gateway"
CLIENT_ID="kong-api"
CLIENT_SECRET="YOUR_CLIENT_SECRET"

echo "=== Step 1: Get access token from Keycloak ==="
TOKEN_RESPONSE=$(curl -s -X POST 
  "${KEYCLOAK_URL}/realms/${REALM}/protocol/openid-connect/token" 
  -H "Content-Type: application/x-www-form-urlencoded" 
  -d "grant_type=password" 
  -d "client_id=${CLIENT_ID}" 
  -d "client_secret=${CLIENT_SECRET}" 
  -d "username=testuser" 
  -d "password=testpassword" 
  -d "scope=openid")

ACCESS_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.access_token')
echo "Token obtained (first 50 chars): ${ACCESS_TOKEN:0:50}..."

echo ""
echo "=== Step 2: Call API without token (expect 401) ==="
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" ${KONG_URL}/api)
echo "Response code: ${HTTP_CODE}"

echo ""
echo "=== Step 3: Call API with valid token (expect 200) ==="
RESPONSE=$(curl -s -w "n%{http_code}" 
  -H "Authorization: Bearer ${ACCESS_TOKEN}" 
  ${KONG_URL}/api)
echo "Response: ${RESPONSE}"

echo ""
echo "=== Step 4: Call API with invalid token (expect 401) ==="
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" 
  -H "Authorization: Bearer invalid.token.here" 
  ${KONG_URL}/api)
echo "Response code: ${HTTP_CODE}"

echo ""
echo "=== Step 5: Check rate limit headers ==="
curl -s -I -H "Authorization: Bearer ${ACCESS_TOKEN}" 
  ${KONG_URL}/api 2>&1 | grep -i "ratelimit"

Production Considerations

Key Rotation

Keycloak rotates signing keys periodically. If you use the open-source JWT plugin, you need to update the consumer credential when keys rotate. The Enterprise OIDC plugin handles this automatically via the JWKS endpoint.

For a managed Keycloak deployment on Skycloak, key rotation is handled for you, and you can monitor signing key changes through audit logs.

High Availability

In production, both Kong and Keycloak should run in clustered mode:

Security Hardening

  • Always use HTTPS between Kong and Keycloak in production
  • Restrict the Kong Admin API to internal networks
  • Enable MFA on the Keycloak admin console
  • Set short token expiration times and use refresh tokens
  • Monitor API access patterns with Skycloak Insights

Declarative Configuration

For GitOps workflows, export your Kong configuration as a declarative YAML file:

# kong.yml
_format_version: "3.0"

services:
  - name: echo-service
    url: http://echo-service:5678
    routes:
      - name: echo-route
        paths:
          - /api
    plugins:
      - name: jwt
        config:
          claims_to_verify:
            - exp
          key_claim_name: iss
      - name: rate-limiting
        config:
          minute: 100
          policy: local
          limit_by: consumer

consumers:
  - username: keycloak-issuer
    jwt_secrets:
      - key: "http://localhost:8080/realms/api-gateway"
        algorithm: RS256
        rsa_public_key: |
          -----BEGIN PUBLIC KEY-----
          YOUR_PUBLIC_KEY_HERE
          -----END PUBLIC KEY-----

Load it with:

kong config db_import kong.yml

Conclusion

Kong and Keycloak together provide a clean separation of concerns: Keycloak handles identity, tokens, and user management, while Kong enforces authentication, rate limiting, and routing at the edge. This keeps your microservices focused on business logic.

The key steps covered in this guide:

  • Setting up both services with Docker Compose
  • Configuring Keycloak as the OIDC provider
  • Validating JWTs at the Kong gateway layer
  • Mapping Kong consumers to Keycloak issuers
  • Applying per-client rate limits
  • Forwarding identity context to upstream services

For further reading, consult the Kong JWT Plugin documentation and the Keycloak Server Administration Guide.

Ready to run Keycloak in production without managing infrastructure? Try Skycloak free and get a fully managed Keycloak cluster with built-in API gateway support.

Guilliano Molaire
Written by Guilliano Molaire Founder

Guilliano is the founder of Skycloak and a cloud infrastructure specialist with deep expertise in product development and scaling SaaS products. He discovered Keycloak while consulting on enterprise IAM and built Skycloak to make managed Keycloak accessible to teams of every size.

Ready to simplify your authentication?

Deploy production-ready Keycloak in minutes. Unlimited users, flat pricing, no SSO tax.

© 2026 Skycloak. All Rights Reserved. Design by Yasser Soliman