a6-plugin-openid-connect
SKILL.md
a6-plugin-openid-connect
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.
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
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
client_id |
string | Yes | — | OAuth 2.0 client ID |
client_secret |
string | Yes | — | OAuth 2.0 client secret (encrypted in etcd) |
discovery |
string | Yes | — | OIDC well-known discovery URL |
Authentication & Scopes
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
scope |
string | No | "openid" |
Space-delimited OIDC scopes |
bearer_only |
boolean | No | false |
Require bearer access token only (no redirect) |
required_scopes |
array | No | — | Scopes required in access token |
realm |
string | No | "apisix" |
Realm in WWW-Authenticate header |
URIs & Redirects
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
redirect_uri |
string | No | {route_uri}/.apisix/redirect |
Redirect URI after auth |
logout_path |
string | No | "/logout" |
Path to trigger logout |
post_logout_redirect_uri |
string | No | — | URL to redirect after logout |
unauth_action |
string | No | "auth" |
Action on unauth: "auth" (redirect), "deny" (401), "pass" (allow) |
Token Verification
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
introspection_endpoint |
string | No | — | Token introspection endpoint URL |
public_key |
string | No | — | PEM public key for local JWT verification |
use_jwks |
boolean | No | false |
Use JWKS from discovery for local JWT verification |
token_signing_alg_values_expected |
string | No | — | Expected JWT signing algorithm |
Session Management
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
session.secret |
string | Yes* | — | 16+ char key for session encryption (*required for auth code flow) |
session.cookie.lifetime |
integer | No | 3600 |
Session cookie lifetime in seconds |
session.storage |
string | No | "cookie" |
"cookie" or "redis" |
Headers to Upstream
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
set_access_token_header |
boolean | No | true |
Set X-Access-Token header |
access_token_in_authorization_header |
boolean | No | false |
Set token in Authorization header |
set_id_token_header |
boolean | No | true |
Set X-ID-Token header |
set_userinfo_header |
boolean | No | true |
Set X-Userinfo header |
hide_credentials |
boolean | No | false |
Remove auth headers before upstream |
Advanced
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
ssl_verify |
boolean | No | false |
Verify IdP SSL certificates |
timeout |
integer | No | 3 |
Request timeout to IdP in seconds |
use_pkce |
boolean | No | false |
Enable PKCE (RFC 7636) |
renew_access_token_on_expiry |
boolean | No | true |
Auto-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
- User visits
http://127.0.0.1:9080/app/dashboard→ no session - APISIX redirects to Keycloak login page
- User authenticates → Keycloak redirects to
http://127.0.0.1:9080/app/redirect?code=... - APISIX exchanges code for tokens, stores in session cookie
- 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
| Provider | Discovery URL Pattern |
|---|---|
| Keycloak | https://{host}/realms/{realm}/.well-known/openid-configuration |
| Auth0 | https://{tenant}.auth0.com/.well-known/openid-configuration |
| Okta | https://{org}.okta.com/.well-known/openid-configuration |
| Azure AD | https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration |
https://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
| Symptom | Cause | Fix |
|---|---|---|
| Redirect loop after login | redirect_uri same as route URI |
Set redirect_uri to a sub-path (e.g., /app/redirect) |
"no session state found" |
Session cookie not saved | Check session.secret length (16+ chars), check SameSite cookie policy |
401 on valid bearer token |
Introspection failing | Verify introspection_endpoint URL, check client credentials |
| SSL errors to IdP | ssl_verify: true but certs invalid |
Fix certs or set ssl_verify: false for testing |
| Large cookie errors | Session too big for cookie | Switch to session.storage: "redis" |
| Token not refreshing | renew_access_token_on_expiry: false |
Set to true (default) |
| NGINX buffer errors | Session cookie too large | Increase 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
Weekly Installs
1
Repository
moonming/a6First Seen
8 days ago
Security Audits
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1