skills/a6-plugin-openid-connect/SKILL.md
Skill for configuring the Apache APISIX openid-connect plugin via the a6 CLI. Covers OIDC authorization code flow, bearer token validation, token introspection vs JWKS verification, session management, provider setup for Keycloak/Auth0/Okta, redirect URIs, and common operational patterns.
npx skillsauth add moonming/a6 a6-plugin-openid-connectInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
| 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 |
| 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 |
| 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) |
| 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 |
| 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" |
| 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 |
| 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 |
APISIX calls the IdP's introspection endpoint for every request.
{
"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"
}
}
APISIX fetches JWKS from the discovery document and validates JWT locally.
{
"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
}
}
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-----"
}
}
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
http://127.0.0.1:9080/app/dashboard → no sessionhttp://127.0.0.1:9080/app/redirect?code=...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
# 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 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 |
| Google | https://accounts.google.com/.well-known/openid-configuration |
{
"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
}
}
}
}
{
"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.
{
"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"
}
}
}
| 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 |
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
tools
Core skill for working with the a6 CLI — the Apache APISIX command-line tool. Provides project conventions, command patterns, architecture overview, and development workflow. Load this skill when working on a6 source code, adding new commands, writing tests, or modifying any a6 component.
tools
Recipe skill for implementing multi-tenant API gateway patterns using the a6 CLI. Covers tenant isolation via Consumer Groups, host/path/header-based routing, per-tenant rate limiting, context forwarding with proxy-rewrite, and declarative config sync workflows for multi-tenant management.
tools
Recipe skill for configuring mutual TLS (mTLS) using the a6 CLI. Covers SSL certificate management, upstream mTLS to backend services, client certificate verification, and end-to-end mTLS setup from client through APISIX to upstream.
tools
Recipe skill for configuring upstream health checks using the a6 CLI. Covers active health checks (HTTP probing), passive health checks (response analysis), combining both, configuring healthy/unhealthy thresholds, and monitoring upstream node status.