Documentsapisix
OIDC Integration
OIDC Integration
Type
Topic
Status
Published
Created
Jul 14, 2026
Updated
Jul 14, 2026

OIDC Integration in APISIX#

APISIX implements full OAuth 2.0 / OpenID Connect flows through the openid-connect plugin, which wraps the lua-resty-openidc library. It handles authorization code grant (browser-based SSO), client credentials grant (M2M), and password grant — with session management, token refresh, and logout built in .

The plugin requires three fields: client_id, client_secret, and discovery . The discovery URL points to the IdP's .well-known/openid-configuration endpoint, from which the plugin auto-discovers token, introspection, and JWKS endpoints.

Key Behavioral Modes#

The plugin operates in one of two modes depending on whether bearer_only is set:

Modebearer_onlyUse caseToken source
Full OIDC / sessionfalse (default)Browser SSO, auth code grantSession cookie; redirects to IdP when absent
Bearer-onlytrueAPIs / M2MAuthorization: Bearer or X-Access-Token header; returns 401 when absent

When bearer_only is false, the plugin calls openidc.authenticate() , which validates any session cookie and, if none is found, redirects the browser to the IdP's authorization endpoint. A session.secret (min 16 chars) is required for cookie encryption; APISIX auto-generates one with a warning if it is omitted .

When bearer_only is true or introspection_endpoint / public_key / use_jwks is set, the plugin calls the internal introspect() function , which routes validation to one of three paths:

  • JWKS: openidc.bearer_jwt_verify() — validates against the IdP's public key set .
  • Public key: same call path using a static public_key value .
  • Introspection endpoint: openidc.introspect() — calls the IdP's introspection endpoint on each request .

Token Header Injection#

After successful validation the plugin injects upstream headers (all configurable, all defaulting to true or false) :

HeaderDefault on?Config key
X-Access-Tokenset_access_token_header
X-ID-Tokenset_id_token_header
X-Userinfoset_userinfo_header
X-Refresh-Tokenset_refresh_token_header

Access token can alternatively be forwarded in the Authorization header via access_token_in_authorization_header: true .

Session & Token Lifecycle#

  • renew_access_token_on_expiry (default true) — silently renews the access token using the refresh token without re-authentication .
  • refresh_session_interval — refresh the ID token without full re-auth .
  • iat_slack (default 120s) — clock skew tolerance on iat claim .
  • jwk_expires_in (default 86400s) — JWK cache TTL .
  • introspection_interval (default 0) — TTL for caching introspection results; 0 means no caching .
  • revoke_tokens_on_logout (default false) — notifies the IdP when the user hits logout_path .

The logout_path (default /logout) triggers logout; post_logout_redirect_uri controls where the user lands after logout .

Claim Validation#

claim_validator enables fine-grained JWT claim checks :

  • issuer.valid_issuers — allowlist of accepted issuers; falls back to the discovery doc's issuer .
  • audience.required — enforce presence of audience claim .
  • audience.match_with_client_id — require aud to include or equal client_id .

required_scopes rejects tokens that don't carry every listed scope .

Keycloak Configuration Pattern#

For Keycloak, the discovery URL is http://<keycloak-host>/realms/<realm-name>/.well-known/openid-configuration .

Authorization code grant (browser SSO):

"openid-connect": {
  "bearer_only": false,
  "session": { "secret": "<min-16-char-key>" },
  "client_id": "<keycloak-client-id>",
  "client_secret": "<keycloak-client-secret>",
  "discovery": "http://<keycloak-host>/realms/<realm>/.well-known/openid-configuration",
  "scope": "openid profile",
  "redirect_uri": "http://<apisix-host>/anything/callback"
}

Enable PKCE by adding "use_pkce": true . Keycloak must have the PKCE challenge method configured on the client.

Client credentials / M2M (JWKS validation):

"openid-connect": {
  "bearer_only": false,
  "use_jwks": true,
  ...
}

Client credentials / M2M (introspection):

"openid-connect": {
  "bearer_only": true,
  ...
}

Breaking change (APISIX 3.16.0): ssl_verify default changed from false to true. Set "ssl_verify": false when using Keycloak with self-signed certificates .

openid-connect vs jwt-auth#

openid-connectjwt-auth
ArchitectureStateful (session / IdP calls)Stateless (local signature verification)
Token issuanceHandled by the IdP, APISIX acts as relying partyTokens issued via APISIX's /apisix/plugin/jwt/sign endpoint
Consumer configPlugin config on route/serviceConsumer in etcd keyed by key claim
RevocationIntrospection or revoke_tokens_on_logoutNo revocation (stateless)
SSO / login redirectYes (bearer_only: false)No
AlgorithmsDetermined by IdP / JWKSHS256, HS512, RS256, ES256
Primary use caseBrowser-facing SSO, delegated auth (OAuth/OIDC)Internal/service APIs with pre-issued tokens

Use openid-connect when APISIX is acting as an OIDC relying party (full login flow, SSO, Keycloak/Auth0/Okta integration). Use jwt-auth when you control token issuance yourself and need lightweight, no-IdP-call verification at the gateway.

OIDC Integration | Dosu