API7 Docs

openid-connect plugin for Apache APISIX

Skill for configuring the APISIX openid-connect plugin via the a6 CLI. Covers authorization-code and bearer flows, PAR, DPoP, and session validation.

Overview

The openid-connect plugin integrates APISIX with external OpenID Connect identity providers (Keycloak, Auth0, Okta, etc.). It supports the full authorization code flow for browser-based applications, bearer token validation for API clients, and token introspection or local JWKS verification.

From APISIX 3.18.0 the plugin also supports nested PAR and DPoP configuration, forwards the raw ID token when set_raw_id_token_header is enabled, fails closed when the trusted issuer cannot be determined, treats claim_validator.audience.match_with_client_id as requiring an audience claim, and enforces required_scopes on authorization-code sessions. For bearer JWT validation, configure claim_validator.issuer.valid_issuers when discovery can be unavailable.

Field tables and a Keycloak PAR/DPoP walkthrough: /hub/openid-connect /apisix/how-to-guide/authentication/secure-oidc-with-par-and-dpop

When to Use

  • Integrate with enterprise identity providers (Keycloak, Auth0, Okta, Azure AD)
  • Browser-based SSO with authorization code flow
  • API protection with bearer access tokens
  • Centralized authentication across multiple routes

Plugin Configuration Reference (Route/Service)

Required Fields

FieldTypeRequiredDefaultDescription
client_idstringYesOAuth 2.0 client ID
client_secretstringConditionalOAuth 2.0 client secret (encrypted in etcd). Optional for local JWT verification, private_key_jwt, or a public-client PKCE flow
discoverystringYesOIDC well-known discovery URL

Authentication & Scopes

FieldTypeRequiredDefaultDescription
scopestringNo"openid"Space-delimited OIDC scopes
bearer_onlybooleanNofalseRequire bearer access token only (no redirect)
required_scopesarrayNoScopes required in access token
realmstringNo"apisix"Realm in WWW-Authenticate header

URIs & Redirects

FieldTypeRequiredDefaultDescription
redirect_uristringNo{route_uri}/.apisix/redirectRedirect URI after auth
logout_pathstringNo"/logout"Path to trigger logout
post_logout_redirect_uristringNoURL to redirect after logout
unauth_actionstringNo"auth"Action on unauth: "auth" (redirect), "deny" (401), "pass" (allow)

Token Verification

FieldTypeRequiredDefaultDescription
introspection_endpointstringNoToken introspection endpoint URL
public_keystringNoPEM public key for local JWT verification
use_jwksbooleanNofalseUse JWKS from discovery for local JWT verification
token_signing_alg_values_expectedstringNoExpected JWT signing algorithm

Session Management

FieldTypeRequiredDefaultDescription
session.secretstringYes*16+ char key for session encryption (*required for auth code flow)
session.absolute_timeoutintegerNoAbsolute session lifetime in seconds
session.cookie.lifetimeintegerNoDeprecated alias for session.absolute_timeout
session.storagestringNo"cookie""cookie" or "redis"

Headers to Upstream

FieldTypeRequiredDefaultDescription
set_access_token_headerbooleanNotrueSet X-Access-Token header
access_token_in_authorization_headerbooleanNofalseSet token in Authorization header
set_id_token_headerbooleanNotrueSet X-ID-Token header
set_raw_id_token_headerbooleanNofalseSet X-Raw-ID-Token with the unmodified ID token
set_userinfo_headerbooleanNotrueSet X-Userinfo header
hide_credentialsbooleanNofalseRemove auth headers before upstream

PAR and DPoP (APISIX 3.18.0+)

Configure these as nested objects. Flat keys such as use_par or use_dpop are rejected.

FieldTypeRequiredDefaultDescription
par.enabledbooleanNofalseSend the authorization request through PAR
dpop.enabledbooleanNofalseBind token requests with a DPoP proof JWT

See the Plugin Hub page for endpoint auth, key material, and validation rules.

Advanced

FieldTypeRequiredDefaultDescription
ssl_verifybooleanNotrueVerify IdP SSL certificates
timeoutintegerNo3Request timeout to IdP in seconds
use_pkcebooleanNofalseEnable PKCE (RFC 7636)
renew_access_token_on_expirybooleanNotrueAuto-refresh expiring tokens

Token Verification Modes

1. Token Introspection (default for bearer_only)

APISIX calls the IdP's introspection endpoint for every request.

  • Pros: Real-time validation, handles token revocation
  • Cons: Added latency (network call to IdP)
{
  "openid-connect": {
    "client_id": "my-app",
    "client_secret": "secret",
    "discovery": "https://keycloak.example.com/realms/my/.well-known/openid-configuration",
    "bearer_only": true,
    "introspection_endpoint": "https://keycloak.example.com/realms/my/protocol/openid-connect/token/introspect"
  }
}

2. Local JWKS Verification

APISIX fetches JWKS from the discovery document and validates JWT locally.

  • Pros: Fast (no per-request IdP call), scalable
  • Cons: Cannot detect revoked tokens until JWKS cache refreshes
{
  "openid-connect": {
    "client_id": "my-app",
    "client_secret": "secret",
    "discovery": "https://keycloak.example.com/realms/my/.well-known/openid-configuration",
    "bearer_only": true,
    "use_jwks": true
  }
}

3. Static Public Key Verification

Provide the public key directly. No discovery or introspection calls.

{
  "openid-connect": {
    "client_id": "my-app",
    "client_secret": "secret",
    "discovery": "https://keycloak.example.com/realms/my/.well-known/openid-configuration",
    "bearer_only": true,
    "public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjAN...\n-----END PUBLIC KEY-----"
  }
}

Step-by-Step: Authorization Code Flow (Keycloak)

1. Create a route with openid-connect

a6 route create -f - <<'EOF'
{
  "id": "oidc-webapp",
  "uri": "/app/*",
  "plugins": {
    "openid-connect": {
      "client_id": "apisix-client",
      "client_secret": "your-client-secret",
      "discovery": "https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration",
      "scope": "openid email profile",
      "redirect_uri": "http://127.0.0.1:9080/app/redirect",
      "session": {
        "secret": "my-16-char-secret"
      }
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "webapp:3000": 1
    }
  }
}
EOF

2. Flow

  1. User visits http://127.0.0.1:9080/app/dashboard → no session
  2. APISIX redirects to Keycloak login page
  3. User authenticates → Keycloak redirects to http://127.0.0.1:9080/app/redirect?code=...
  4. APISIX exchanges code for tokens, stores in session cookie
  5. Subsequent requests use the session cookie automatically

Step-by-Step: Bearer Token API Protection

1. Create a route for API protection

a6 route create -f - <<'EOF'
{
  "id": "oidc-api",
  "uri": "/api/*",
  "plugins": {
    "openid-connect": {
      "client_id": "apisix-client",
      "client_secret": "your-client-secret",
      "discovery": "https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration",
      "bearer_only": true,
      "use_jwks": true
    }
  },
  "upstream": {
    "type": "roundrobin",
    "nodes": {
      "backend:8080": 1
    }
  }
}
EOF

2. Obtain and use token

# Get token from IdP
TOKEN=$(curl -s -X POST \
  "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/token" \
  -d "client_id=apisix-client" \
  -d "client_secret=your-client-secret" \
  -d "grant_type=client_credentials" \
  | jq -r '.access_token')

# Call the API
curl -i http://127.0.0.1:9080/api/resource \
  -H "Authorization: Bearer ${TOKEN}"

Provider Discovery URLs

ProviderDiscovery URL Pattern
Keycloakhttps://{host}/realms/{realm}/.well-known/openid-configuration
Auth0https://{tenant}.auth0.com/.well-known/openid-configuration
Oktahttps://{org}.okta.com/.well-known/openid-configuration
Azure ADhttps://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration
Googlehttps://accounts.google.com/.well-known/openid-configuration

Common Patterns

Redis session storage (distributed APISIX)

{
  "openid-connect": {
    "client_id": "my-app",
    "client_secret": "secret",
    "discovery": "https://idp.example.com/.well-known/openid-configuration",
    "session": {
      "secret": "my-16-char-secret",
      "storage": "redis",
      "redis": {
        "host": "redis.example.com",
        "port": 6379,
        "password": "redis-pass",
        "database": 0
      }
    }
  }
}

Allow unauthenticated access (optional auth)

{
  "openid-connect": {
    "client_id": "my-app",
    "client_secret": "secret",
    "discovery": "https://idp.example.com/.well-known/openid-configuration",
    "bearer_only": true,
    "unauth_action": "pass"
  }
}

Authenticated requests get identity headers; unauthenticated requests pass through without identity.

PKCE for public clients

{
  "openid-connect": {
    "client_id": "spa-client",
    "client_secret": "secret",
    "discovery": "https://idp.example.com/.well-known/openid-configuration",
    "use_pkce": true,
    "session": {
      "secret": "my-16-char-secret"
    }
  }
}

Troubleshooting

SymptomCauseFix
Redirect loop after loginredirect_uri same as route URISet redirect_uri to a sub-path (e.g., /app/redirect)
"no session state found"Session cookie not savedCheck session.secret length (16+ chars), check SameSite cookie policy
401 on valid bearer tokenIntrospection failingVerify introspection_endpoint URL, check client credentials
SSL errors to IdPssl_verify: true but certs invalidFix certs or set ssl_verify: false for testing
Large cookie errorsSession too big for cookieSwitch to session.storage: "redis"
Token not refreshingrenew_access_token_on_expiry: falseSet to true (default)
403 with required_scopes after loginSession scopes missing or unreadable (3.18.0+)Confirm granted scopes on the access or ID token; required_scopes applies to authorization-code sessions
Bearer JWT rejected while discovery is downIssuer fail-closed (3.18.0+)Set claim_validator.issuer.valid_issuers
NGINX buffer errorsSession cookie too largeIncrease proxy_buffers / proxy_buffer_size in NGINX config

Config Sync Example

version: "1"
routes:
  - id: oidc-webapp
    uri: /app/*
    plugins:
      openid-connect:
        client_id: apisix-client
        client_secret: your-client-secret
        discovery: https://keycloak.example.com/realms/myrealm/.well-known/openid-configuration
        scope: openid email profile
        redirect_uri: http://127.0.0.1:9080/app/redirect
        session:
          secret: my-16-char-secret
    upstream_id: webapp-upstream
upstreams:
  - id: webapp-upstream
    type: roundrobin
    nodes:
      "webapp:3000": 1

This page is generated from a6-plugin-openid-connect/SKILL.md in the api7/a6 repository. Browse all skills on the AI Agent Skills page.