openid-connect plugin for API7 Gateway Skill for configuring the API7 Enterprise Edition (API7 EE) openid-connect plugin via the a7 CLI. Covers OIDC authorization code flow, bearer token vali…
The openid-connect plugin integrates API7 EE 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.
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
Field Type Required Default Description client_idstring Yes — OAuth 2.0 client ID client_secretstring Yes — OAuth 2.0 client secret (encrypted in the database) discoverystring Yes — OIDC well-known discovery URL
Field Type Required Default Description scopestring No "openid"Space-delimited OIDC scopes bearer_onlyboolean No falseRequire bearer access token only (no redirect) required_scopesarray No — Scopes required in access token realmstring No "apisix"Realm in WWW-Authenticate header
Field Type Required Default Description redirect_uristring No {route_uri}/.apisix/redirectRedirect URI after auth logout_pathstring No "/logout"Path to trigger logout post_logout_redirect_uristring No — URL to redirect after logout unauth_actionstring No "auth"Action on unauth: "auth" (redirect), "deny" (401), "pass" (allow)
Field Type Required Default Description introspection_endpointstring No — Token introspection endpoint URL public_keystring No — PEM public key for local JWT verification use_jwksboolean No falseUse JWKS from discovery for local JWT verification token_signing_alg_values_expectedstring No — Expected JWT signing algorithm
Field Type Required Default Description session.secretstring Yes* — 16+ char key for session encryption (*required for auth code flow) session.cookie.lifetimeinteger No 3600Session cookie lifetime in seconds session.storagestring No "cookie""cookie" or "redis"
Field Type Required Default Description set_access_token_headerboolean No trueSet X-Access-Token header access_token_in_authorization_headerboolean No falseSet token in Authorization header set_id_token_headerboolean No trueSet X-ID-Token header set_userinfo_headerboolean No trueSet X-Userinfo header hide_credentialsboolean No falseRemove auth headers before upstream
Field Type Required Default Description ssl_verifyboolean No falseVerify IdP SSL certificates timeoutinteger No 3Request timeout to IdP in seconds use_pkceboolean No falseEnable PKCE (RFC 7636) renew_access_token_on_expiryboolean No trueAuto-refresh expiring tokens
API7 EE 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"
}
}
API7 EE 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
}
}
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----- \n MIIBIjAN... \n -----END PUBLIC KEY-----"
}
}
a7 route create -g default -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": [{"host": "webapp", "port": 3000, "weight": 1}]
}
}
EOF
User visits http://127.0.0.1:9080/app/dashboard → no session
API7 EE redirects to Keycloak login page
User authenticates → Keycloak redirects to http://127.0.0.1:9080/app/redirect?code=...
API7 EE exchanges code for tokens, stores in session cookie
Subsequent requests use the session cookie automatically
a7 route create -g default -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": [{"host": "backend", "port": 8080, "weight": 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-configurationAuth0 https://{tenant}.auth0.com/.well-known/openid-configurationOkta https://{org}.okta.com/.well-known/openid-configurationAzure AD https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configurationGoogle 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 URISet 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 tokenIntrospection failing Verify introspection_endpoint URL, check client credentials SSL errors to IdP ssl_verify: true but certs invalidFix 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: falseSet to true (default)
version : "1"
gateway_groups :
- name : default
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 :
type : roundrobin
nodes :
- host : webapp
port : 3000
weight : 1
This page is generated from a7-plugin-openid-connect/SKILL.md in the api7/a7 repository. Browse all skills on the AI Agent Skills page.