Skip to main content

Set Up SSO with Keycloak

OpenID Connect (OIDC) is a simple identity layer on top of the OAuth 2.0 protocol. It allows clients to verify the identity of end users based on the authentication performed by the identity provider, as well as to obtain basic profile information about end users in an interoperable and REST-like manner. With APISIX and Keycloak, you can implement OIDC-based authentication processes to protect your APIs and enable single sign-on (SSO).

Keycloak is an open-source identity and access management solution for modern applications and services. Keycloak supports single sign-on (SSO), which enables services to interface with Keycloak through protocols such as OIDC and OAuth 2.0. In addition, Keycloak also supports delegating authentication to third party identity providers such as Facebook and Google.

The guide will show you how to integrate APISIX with Keycloak using authorization code grant, client credentials grant, and password grant, using the openid-connect plugin.

Diagram of APISIX and Keycloak

Prerequisite(s)

  • Install Docker.
  • Install cURL to send requests to the services for validation.
  • Follow the Getting Started tutorial to start a new APISIX instance in Docker or on Kubernetes.

Configure Keycloak

Start a Keycloak instance named apisix-quickstart-keycloak with the administrator name quickstart-admin and password quickstart-admin-pass in development mode:

docker run -d --name "apisix-quickstart-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.1 start-dev

Open http://localhost:8080/admin/ and sign in with the administrator username quickstart-admin and password quickstart-admin-pass. Keep the Admin Console session open for the following steps.

Create a Realm

Realms in Keycloak are workspaces for managing resources such as users, credentials, and roles. Resources in different realms are isolated from each other. Create a realm named quickstart-realm for APISIX:

  1. Select Manage realms in the left menu, then select Create realm.
  2. Enter quickstart-realm in Realm name.
  3. Select Create.

Create a realm in Keycloak 26.7.1

Create a Client

Clients in Keycloak are applications and services that request authentication. APISIX acts as a client when it starts an OIDC authentication flow. Create a client named apisix-quickstart-client:

  1. Select Clients in the left menu, then select Create client.

  2. Keep Client type set to OpenID Connect, enter apisix-quickstart-client in Client ID, and select Next.

    Configure general client settings in Keycloak 26.7.1

  3. In Capability config, turn on Client authentication. Keep Standard flow selected, then select Direct access grants and Service account roles. These capabilities enable the flows demonstrated in this guide.

    Enable the client capabilities used in this guide

  4. Select Next, enter http://localhost:9080/anything/callback in Valid redirect URIs, and select Save.

    Configure the APISIX redirect URI in Keycloak 26.7.1

Leave Require PKCE off while testing the standard authorization code flow. The PKCE variant shows when to turn it on.

Create a User

Users in Keycloak are entities that can log in. They can have attributes such as a username, email address, and name.

If you are only implementing client credentials grant, you can skip this section.

Create a user whose profile satisfies the default Keycloak user-profile requirements:

  1. Select Users in the left menu, then select Create new user.

  2. Configure the following fields and select Create:

    FieldValue
    Usernamequickstart-user
    Emailquickstart-user@example.com
    First nameQuickstart
    Last nameUser

    Create a complete user profile in Keycloak 26.7.1

  3. Open the Credentials tab and select Set password.

  4. Enter quickstart-user-pass in Password and Password confirmation, turn off Temporary, and select Save.

  5. Select Save password in the confirmation dialog.

Set a permanent user password in Keycloak 26.7.1

Obtain the OIDC Configuration

In this section, you will obtain the OIDC configuration from Keycloak and define it as shell variables. Later steps use these variables in shell commands.

info

Open a separate terminal and define the shell variables there. Run the later shell commands in the same terminal.

Get Discovery Endpoint

Select Realm settings in the left menu. In the General tab, find Endpoints, then copy the link for OpenID Endpoint Configuration.

Find the OpenID discovery endpoint in Keycloak 26.7.1

The link should be the same as the following:

http://localhost:8080/realms/quickstart-realm/.well-known/openid-configuration

Configuration values exposed with this endpoint are required during OIDC authentication.

Replace the address with a host IP that the APISIX container can reach, then save the values to environment variables:

export KEYCLOAK_IP=192.168.42.145 # replace with your host IP
export OIDC_DISCOVERY="http://${KEYCLOAK_IP}:8080/realms/quickstart-realm/.well-known/openid-configuration"

Get Client ID and Secret

Select Clients > apisix-quickstart-client > Credentials, then copy the value in Client Secret.

Copy the client secret in Keycloak 26.7.1

Save the OIDC client ID and secret to environment variables:

export OIDC_CLIENT_ID=apisix-quickstart-client
export OIDC_CLIENT_SECRET=replace-with-your-client-secret

Implement Authorization Code Grant

The authorization code grant is used by web and mobile applications. When a user requests a protected resource, APISIX redirects the browser to Keycloak for sign-in. Keycloak returns a short-lived authorization code, which APISIX exchanges for tokens and stores in a cookie-backed session.

To implement authorization code grant, create a route with the openid-connect plugin as follows:

curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT --data-binary @- <<EOF
{
"id": "auth-with-oidc",
"uri": "/anything/*",
"plugins": {
"openid-connect": {
"bearer_only": false,
"session": {
"secret": "f86cf31663a9c9fa0a28c2cc78badef1"
},
"client_id": "$OIDC_CLIENT_ID",
"client_secret": "$OIDC_CLIENT_SECRET",
"discovery": "$OIDC_DISCOVERY",
"scope": "openid profile",
"redirect_uri": "http://localhost:9080/anything/callback"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}
EOF

bearer_only: Set to false for authorization code grant.

session.secret: Replace with your key used for session encryption and HMAC operation. Required when bearer_only is false.

client_id: Keycloak client ID.

client_secret: Keycloak client secret.

discovery: URI to discovery document.

redirect_uri: URI to redirect to after authentication with Keycloak. See Access settings.

Verify Standard Authorization Code Grant

Navigate to http://localhost:9080/anything/test in a browser. APISIX redirects the request to the Keycloak sign-in page:

Sign in to the Keycloak realm

Sign in with the username quickstart-user and password quickstart-user-pass. Keycloak returns an authorization code, APISIX exchanges it for tokens, and the request is forwarded to httpbin.org. You should receive a response similar to the following:

{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "text/html..."
},
"json": null,
"method": "GET",
"origin": "127.0.0.1",
"url": "http://127.0.0.1/anything/test"
}

Refresh the page or open another URL under /anything/. APISIX reuses the browser session cookie, so Keycloak does not prompt you to sign in again while the session remains valid.

To implement authorization code grant with PKCE, first open Clients > apisix-quickstart-client > Settings > Capability config, turn on Require PKCE, and select Save. Then create a route similar to the previous example, but enable use_pkce:

curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT --data-binary @- <<EOF
{
"id": "auth-with-oidc",
"uri": "/anything/*",
"plugins": {
"openid-connect": {
"bearer_only": false,
"session": {
"secret": "f86cf31663a9c9fa0a28c2cc78badef1"
},
"use_pkce": true,
"client_id": "$OIDC_CLIENT_ID",
"client_secret": "$OIDC_CLIENT_SECRET",
"discovery": "$OIDC_DISCOVERY",
"scope": "openid profile",
"redirect_uri": "http://localhost:9080/anything/callback"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}
EOF

use_pkce: Set to true to send an S256 PKCE challenge during authorization.

Verify Authorization Code Grant With PKCE

Open a new browser session and navigate to http://localhost:9080/anything/test. Sign in with the username quickstart-user and password quickstart-user-pass. Keycloak requires PKCE for this client, so an HTTP/1.1 200 OK response from the upstream verifies that APISIX sent an S256 challenge and completed the code exchange.

Verify with Invalid Credentials

Open another new private browser session, navigate to http://localhost:9080/anything/test, and sign in with the wrong credentials. You should see an authentication failure:

Keycloak rejects invalid user credentials

Implement Client Credentials Grant

In client credentials grant, clients obtain access tokens without any users involved. It is typically used in machine-to-machine (M2M) communications.

To implement client credentials grant, create a route with the openid-connect plugin as follows:

curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT --data-binary @- <<EOF
{
"id": "auth-with-oidc",
"uri": "/anything/*",
"plugins": {
"openid-connect": {
"bearer_only": true,
"use_jwks": true,
"client_id": "$OIDC_CLIENT_ID",
"discovery": "$OIDC_DISCOVERY"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}
EOF

❶ Verify bearer tokens locally with the JWKS endpoint from the discovery document. APISIX does not call the token or introspection endpoint in this mode. Therefore, the route does not need client_secret or session configuration.

The client still uses $OIDC_CLIENT_SECRET when it requests an access token from Keycloak. The secret is omitted only from the APISIX route because APISIX validates the issued JWT through JWKS.

Alternatively, you can use Keycloak's introspection endpoint to verify the token. Keycloak 26.7.1 requires the client performing introspection to be included in the token audience. Add an audience mapper before configuring APISIX:

  1. Open Clients > apisix-quickstart-client > Client scopes.
  2. Select apisix-quickstart-client-dedicated > Configure a new mapper > Audience.
  3. Enter apisix-audience in Name and select apisix-quickstart-client in Included Client Audience.
  4. Keep Add to access token on and select Save.

Configure the audience required for token introspection

Create the route without use_jwks so that APISIX uses the introspection endpoint from the discovery document:

curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT --data-binary @- <<EOF
{
"id": "auth-with-oidc",
"uri": "/anything/*",
"plugins": {
"openid-connect": {
"bearer_only": true,
"client_id": "$OIDC_CLIENT_ID",
"client_secret": "$OIDC_CLIENT_SECRET",
"discovery": "$OIDC_DISCOVERY"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}
EOF

APISIX obtains the introspection endpoint from the discovery document.

Verify With Valid Access Token

Obtain an access token for the Keycloak server at the token endpoint:

curl -i "http://${KEYCLOAK_IP}:8080/realms/quickstart-realm/protocol/openid-connect/token" -X POST \
-d 'grant_type=client_credentials' \
-d "client_id=$OIDC_CLIENT_ID" \
-d "client_secret=$OIDC_CLIENT_SECRET"

The expected response is similar to the following:

{
"access_token": "eyJ...",
"expires_in": 300,
"refresh_expires_in": 0,
"token_type": "Bearer",
"scope": "email profile"
}

If you configured the introspection variant, the access token includes apisix-quickstart-client in its aud claim.

Save the access token to an environment variable:

# replace with your access token
export ACCESS_TOKEN="replace-with-your-access-token"

Send a request to the route with the valid access token:

curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer $ACCESS_TOKEN"

An HTTP/1.1 200 OK response verifies that the request to the upstream resource was authorized.

Verify With Invalid Access Token

Send a request to the route with invalid access token:

curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer invalid-access-token"

An HTTP/1.1 401 Unauthorized response verifies that the OIDC plugin rejects requests with invalid access token.

Verify without Access Token

Send a request to the route without access token:

curl -i "http://127.0.0.1:9080/anything/test"

An HTTP/1.1 401 Unauthorized response verifies that the OIDC plugin rejects requests without access token.

Implement Password Grant

Password grant is a legacy approach to exchange user credentials for an access token.

To implement password grant, create a route with the openid-connect plugin as follows:

curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT --data-binary @- <<EOF
{
"id": "auth-with-oidc",
"uri": "/anything/*",
"plugins": {
"openid-connect": {
"bearer_only": true,
"use_jwks": true,
"client_id": "$OIDC_CLIENT_ID",
"client_secret": "$OIDC_CLIENT_SECRET",
"discovery": "$OIDC_DISCOVERY",
"scope": "openid profile"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}
EOF

bearer_only: Set to true so APISIX rejects requests without a bearer token instead of starting an authorization code flow.

use_jwks: Verify bearer tokens locally with the JWKS endpoint from the discovery document.

Verify With Valid Access Token

Obtain an access token for the Keycloak server at the token endpoint:

OIDC_USER=quickstart-user
OIDC_PASSWORD=quickstart-user-pass
curl -i "http://${KEYCLOAK_IP}:8080/realms/quickstart-realm/protocol/openid-connect/token" -X POST \
-d 'grant_type=password' \
-d "client_id=$OIDC_CLIENT_ID" \
-d "client_secret=$OIDC_CLIENT_SECRET" \
-d "username=$OIDC_USER" \
-d "password=$OIDC_PASSWORD"

The expected response is similar to the following:

{
"access_token": "eyJ...",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJ...",
"token_type": "Bearer",
"scope": "email profile"
}

Save the access token and refresh token to environment variables. The refresh token will be used in the refresh token step.

# replace with your access token
export ACCESS_TOKEN="replace-with-your-access-token"
export REFRESH_TOKEN="replace-with-your-refresh-token"

Send a request to the route with the valid access token:

curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer $ACCESS_TOKEN"

An HTTP/1.1 200 OK response verifies that the request to the upstream resource was authorized.

Verify With Invalid Access Token

Send a request to the route with invalid access token:

curl -i "http://127.0.0.1:9080/anything/test" -H "Authorization: Bearer invalid-access-token"

An HTTP/1.1 401 Unauthorized response verifies that the OIDC plugin rejects requests with invalid access token.

Verify without Access Token

Send a request to the route without access token:

curl -i "http://127.0.0.1:9080/anything/test"

An HTTP/1.1 401 Unauthorized response verifies that the OIDC plugin rejects requests without access token.

Refresh Token

To refresh the access token, send the following request to the Keycloak token endpoint:

curl -i "http://${KEYCLOAK_IP}:8080/realms/quickstart-realm/protocol/openid-connect/token" -X POST \
-d 'grant_type=refresh_token' \
-d "client_id=$OIDC_CLIENT_ID" \
-d "client_secret=$OIDC_CLIENT_SECRET" \
-d "refresh_token=$REFRESH_TOKEN"

You should see a response with a new access token and refresh token:

{
"access_token": "eyJ...",
"expires_in": 300,
"refresh_expires_in": 1800,
"refresh_token": "eyJ...",
"token_type": "Bearer",
"scope": "email profile"
}

Replace both environment variables with the newly returned values before sending subsequent requests or refreshing the token again:

export ACCESS_TOKEN="replace-with-your-new-access-token"
export REFRESH_TOKEN="replace-with-your-new-refresh-token"

Next Steps

To harden the authorization code flow with PAR, DPoP, PKCE, and private-key JWT authentication, see Secure OIDC with PAR and DPoP.

APISIX supports the integration with many other OIDC identity providers, such as Okta, Auth0, Authgear, and Microsoft Entra ID (Azure AD).

In addition, APISIX also supports built-in authentication approaches such as key authentication, basic authentication, and JWT.