Set Up SSO with Keycloak
OpenID Connect (OIDC) adds an identity layer to OAuth 2.0, allowing applications to verify an end user's identity and obtain basic profile information from an identity provider (IdP). In a single sign-on (SSO) deployment, users authenticate through the IdP and can access connected applications without signing in separately to each one.
Keycloak is an open-source identity and access management platform for applications and services. It can manage users directly or connect to external identity providers, then provide centralized authentication through OIDC or SAML. In this integration, Apache APISIX delegates browser authentication to Keycloak before proxying requests to an upstream service.
The guide shows how to configure Keycloak and APISIX for the OIDC Authorization Code flow with Proof Key for Code Exchange (PKCE). When a request does not have a valid APISIX session, APISIX redirects the browser to Keycloak. After successful authentication, APISIX exchanges the authorization code for tokens, creates a browser session, and resumes the original request.
Prerequisite(s)
- Install Docker.
- Install cURL and OpenSSL.
- Follow the Getting Started tutorial to start APISIX with Docker.
- If you plan to use ADC, install and configure ADC before continuing.
Configure Keycloak
Start a local Keycloak server, then create a realm, an OIDC client, and a user for browser authentication.
Start Keycloak
Start Keycloak in development mode with a temporary administrator account:
docker run -d --name apisix-keycloak \
-e 'KC_BOOTSTRAP_ADMIN_USERNAME=quickstart-admin' \
-e 'KC_BOOTSTRAP_ADMIN_PASSWORD=quickstart-admin-pass' \
-p 8080:8080 \
quay.io/keycloak/keycloak:26.7.3 start-dev
Development mode and the example credentials are intended only for local testing. For a production deployment, use HTTPS, a production database, and a permanent administrator account.
Open http://localhost:8080/admin/ and sign in with the administrator username quickstart-admin and password quickstart-admin-pass.
Create a Realm
A Keycloak realm isolates users, clients, roles, and other authentication resources. Create a realm for this guide:
- Select Manage realms → Create realm.
- Enter
quickstart-realmas the realm name. - Select Create.

Create an OIDC Client
Register APISIX as a confidential OIDC client:
-
Select Clients → Create client.
-
Keep Client type set to OpenID Connect, enter
apisix-quickstart-clientas the client ID, and select Next.
-
Turn on Client authentication and Standard flow. Leave the other authentication flows off, then select Next.
-
Enter
http://localhost:9080/anything/user/callbackunder Valid redirect URIs and select Save.
-
In Capability config, turn on Require PKCE, select S256 as the PKCE method, and select Save.
The redirect URI identifies the APISIX endpoint where Keycloak returns the browser after authentication. In production, use an HTTPS endpoint that users can reach and register that exact URI in Keycloak.
Create a User
Create a user that can sign in through the new realm:
-
Select Users → Create new user.
-
Configure the following fields and select Create:
Field Value Username quickstart-userEmail quickstart-user@example.comFirst name QuickstartLast name User
-
Open the Credentials tab and select Set password.
-
Enter
quickstart-user-passin Password and Password confirmation, turn off Temporary, and select Save. -
Select Save password in the confirmation dialog.

Save the OIDC Configuration
Select Realm settings. On the General tab, find Endpoints and open OpenID Endpoint Configuration.

The discovery endpoint has the following path:
/realms/quickstart-realm/.well-known/openid-configuration
The APISIX container and the browser must be able to reach Keycloak at the same hostname. Save a reachable host address and the discovery endpoint as environment variables, replacing the example address:
export KEYCLOAK_HOST=192.168.1.100
export KEYCLOAK_DISCOVERY="http://${KEYCLOAK_HOST}:8080/realms/quickstart-realm/.well-known/openid-configuration"
Select Clients → apisix-quickstart-client → Credentials and copy the client secret.

Save the client ID and secret as environment variables:
export KEYCLOAK_CLIENT_ID=apisix-quickstart-client
export KEYCLOAK_CLIENT_SECRET=replace-with-your-client-secret
Keep the client secret confidential. Store production credentials in a secret manager and rotate them according to the organization's credential-rotation policy.
Configure APISIX
Configure a route that authenticates browser requests before forwarding them to httpbin.org, a public HTTP request and response service. The /anything/user/* endpoint returns request details for verification.
Generate a unique secret that APISIX will use to encrypt and authenticate the browser session cookie:
export APISIX_SESSION_SECRET="$(openssl rand -hex 32)"
Choose either the Admin API or ADC to configure the route.
- Admin API
- ADC
Create the route through the Admin API:
curl "http://127.0.0.1:9180/apisix/admin/routes/keycloak-sso" -X PUT \
--data-binary @- <<EOF
{
"uri": "/anything/user/*",
"plugins": {
"openid-connect": {
"client_id": "$KEYCLOAK_CLIENT_ID",
"client_secret": "$KEYCLOAK_CLIENT_SECRET",
"discovery": "$KEYCLOAK_DISCOVERY",
"redirect_uri": "http://localhost:9080/anything/user/callback",
"bearer_only": false,
"use_pkce": true,
"scope": "openid profile email",
"session": {
"secret": "$APISIX_SESSION_SECRET"
},
"set_access_token_header": false,
"set_id_token_header": false,
"set_userinfo_header": false
},
"proxy-rewrite": {
"headers": {
"remove": ["Authorization", "Cookie"]
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}
EOF
❶ discovery: URI of the Keycloak realm's OIDC discovery document.
❷ redirect_uri: URI where Keycloak returns the browser after authentication. It must match the valid redirect URI configured for the Keycloak client.
❸ bearer_only and use_pkce: Start browser authentication when no valid session exists and send an S256 PKCE challenge during authorization.
❹ set_access_token_header, set_id_token_header, and set_userinfo_header: Set to false to prevent APISIX from adding tokens and user information to upstream request headers.
❺ proxy-rewrite.headers.remove: Removes the original authorization header and the entire cookie header, including the APISIX session cookie, before proxying the request. Review this setting if the upstream application requires cookies.
Create an adc.yaml file with the route configuration:
services:
- name: keycloak-sso
routes:
- name: keycloak-sso
uris:
- /anything/user/*
plugins:
openid-connect:
client_id: "${KEYCLOAK_CLIENT_ID}"
client_secret: "${KEYCLOAK_CLIENT_SECRET}"
discovery: "${KEYCLOAK_DISCOVERY}"
redirect_uri: http://localhost:9080/anything/user/callback
bearer_only: false
use_pkce: true
scope: openid profile email
session:
secret: "${APISIX_SESSION_SECRET}"
set_access_token_header: false
set_id_token_header: false
set_userinfo_header: false
proxy-rewrite:
headers:
remove:
- Authorization
- Cookie
upstream:
type: roundrobin
nodes:
- host: httpbin.org
port: 80
weight: 1
❶ discovery: URI of the Keycloak realm's OIDC discovery document.
❷ redirect_uri: URI where Keycloak returns the browser after authentication. It must match the valid redirect URI configured for the Keycloak client.
❸ bearer_only and use_pkce: Start browser authentication when no valid session exists and send an S256 PKCE challenge during authorization.
❹ set_access_token_header, set_id_token_header, and set_userinfo_header: Set to false to prevent APISIX from adding tokens and user information to upstream request headers.
❺ proxy-rewrite.headers.remove: Removes the original authorization header and the entire cookie header, including the APISIX session cookie, before proxying the request. Review this setting if the upstream application requires cookies.
ADC reconciles services as desired state. The label selector limits this example to its own labeled resources. Preview the scoped changes and confirm that they contain no unintended updates or deletions:
adc diff -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=keycloak-sso
Synchronize the reviewed service configuration:
adc sync -f adc.yaml \
--include-resource-type service \
--label-selector docs-example=keycloak-sso
Verify Authentication
Navigate to http://localhost:9080/anything/user/get in a browser. APISIX redirects you to Keycloak. Sign in with the username quickstart-user and password quickstart-user-pass.

After successful authentication, Keycloak returns the browser to APISIX, and APISIX forwards the request to httpbin.org. The response should contain fields similar to these:
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate",
"Accept-Language": "en-CA,en-US;q=0.9,en;q=0.8",
"Host": "localhost",
"Priority": "u=0, i",
"Sec-Fetch-Dest": "document",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Site": "cross-site",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 ...",
"X-Amzn-Trace-Id": "Root=1-...",
"X-Forwarded-Host": "localhost:9080"
},
"json": null,
"method": "GET",
"origin": "192.168.155.1, xxx.xxx.xxx.xxx",
"url": "http://localhost:9080/anything/user/get"
}
Header values and the reported origin address vary by browser and network environment.
The upstream request headers should not include the APISIX session cookie, access token, ID token, or user-information header. Reload the page to verify that APISIX reuses the browser session without redirecting you to Keycloak.
Next Steps
You have now configured APISIX to authenticate browser requests with Keycloak. To authorize requests from services without an end-user session, see Authorize M2M Requests with Keycloak. See the openid-connect plugin reference for more configuration options.