Skip to main content

openid-connect

Authenticates users against an OpenID Connect provider.

Category AuthenticationAdd it to Route, Service, Consumer, Consumer group, Global rule, Plugin configEngine plugin openid-connectPriority 2599

What it does​

openid-connect protects the route it is added to with an OpenID Connect provider, Keycloak or Entra ID for example, in one of two modes. With Bearer only off, a browser without a session is sent to the provider to sign in. It comes back to the Redirect URI with a code the gateway exchanges for tokens, and keeps a session cookie from then on. With Bearer only on, every request has to carry a bearer token in its Authorization header, which the gateway checks against the provider's published keys or by asking the provider.

A request that passes reaches the backend with the access token in X-Access-Token, the user information in X-Userinfo and, after a browser sign-in, the ID token's claims in X-ID-Token. A request that fails is sent to sign in, answered 401, or answered 403 when the token lacks a scope under Required scopes.

When to use it​

  • People sign in with the company identity provider before they reach an internal web application behind the gateway.
  • API clients call a route with the bearer token the provider issued them, and the backend trusts the gateway's check instead of verifying tokens itself.
  • The backend needs to know who the user is, and reads it from the headers the gateway adds.

Not for tokens your own consumers hold, signed without an identity provider — see jwt-auth (Authenticates consumers with a JSON Web Token they present), fine-grained permissions decided by Keycloak's authorization services — see authz-keycloak (Authorizes requests with Keycloak's authorization services) or a SAML identity provider — see saml-auth (Authenticates users with a SAML identity provider).

Guides​

Pages that walk through a job with openid-connect, step by step.

Set it up​

The callback path has to be one this route serves, as /orders/callback is under a route for /orders/*.

Under Basics set Discovery to https://idp.example.com/.well-known/openid-configuration, set Client ID to orders-api, set Client secret to <secret>, turn off Bearer only, set Redirect URI to https://api.example.com/orders/callback, set Scope to openid profile email and fill Session as in the JSON below.

The console's Add plugin panel, filled with this example.
openid-connect · as the console saves it
{
"discovery": "https://idp.example.com/.well-known/openid-configuration",
"client_id": "orders-api",
"client_secret": "<secret>",
"bearer_only": false,
"redirect_uri": "https://api.example.com/orders/callback",
"scope": "openid profile email",
"session": {
"secret": "<secret>",
"cookie_name": "gateway_session",
"cookie_http_only": true,
"cookie_secure": true
}
}
  • A browser without a session is answered 302 to the provider's sign-in page, asking for the scope you wrote and naming the redirect_uri the provider sends it back to.
  • The answer sets the gateway_session cookie, marked Secure and HttpOnly, which carries the sign-in state until the provider sends the browser back to the callback.
  • Once the person has signed in at the provider, the browser comes back to the callback and the gateway sends it on to the address it first asked for. The request then goes on to your backend with the access token in X-Access-Token, the ID token's claims in X-ID-Token and the user information in X-Userinfo.
  • Decoded from Base64, X-Userinfo and X-ID-Token are JSON objects of the provider's claims about the person, such as preferred_username; X-ID-Token also names the provider in iss.

More scenarios​

Accept only bearer tokens and check their signature against the provider's keys

Under Basics set Discovery to https://idp.example.com/.well-known/openid-configuration, set Client ID to orders-api and turn on Bearer only. Under Behavior turn on Use JWKS and fill Required scopes as in the JSON below.

openid-connect · as the console saves it
{
"discovery": "https://idp.example.com/.well-known/openid-configuration",
"client_id": "orders-api",
"bearer_only": true,
"use_jwks": true,
"required_scopes": [
"orders:read"
]
}

What you will see​

  • A request without a bearer token is answered 401 with a WWW-Authenticate: Bearer challenge naming the realm, and so is one whose token the provider did not sign, with an error_description that says why.
  • A token the provider signed that carries every required scope passes: the backend receives it in X-Access-Token, the user information in X-Userinfo, and the Authorization header as the client sent it.
  • A signed token that lacks a required scope is answered 403 with a JSON error naming the scope as not present.
Keep signed-in sessions in Redis

The callback path has to be one this route serves, as /orders/callback is under a route for /orders/*.

Under Basics set Discovery to https://idp.example.com/.well-known/openid-configuration, set Client ID to gateway, set Client secret to <secret>, turn off Bearer only, set Redirect URI to https://gateway.example.com/orders/callback and fill Session as in the JSON below.

openid-connect · as the console saves it
{
"discovery": "https://idp.example.com/.well-known/openid-configuration",
"client_id": "gateway",
"client_secret": "<secret>",
"bearer_only": false,
"redirect_uri": "https://gateway.example.com/orders/callback",
"session": {
"secret": "<secret>",
"storage": "redis",
"redis": {
"host": "redis.internal",
"port": 6379,
"password": "<secret>"
}
}
}

What you will see​

  • A browser without a session is answered 302 to the provider's sign-in page and gets the session cookie once Redis has taken the session, so a missing cookie means Redis did not.
Sign users out at a path this route serves and send them back to the site

Like the callback, the logout path has to be one this route serves: /orders/logout under a route for /orders/*, where the default /logout is not.

Under Basics set Discovery to https://idp.example.com/.well-known/openid-configuration, set Client ID to gateway, set Client secret to <secret>, turn off Bearer only, set Redirect URI to https://gateway.example.com/orders/callback and fill Session as in the JSON below. Under Behavior set Logout path to /orders/logout and set Post logout redirect URI to https://gateway.example.com/.

openid-connect · as the console saves it
{
"discovery": "https://idp.example.com/.well-known/openid-configuration",
"client_id": "gateway",
"client_secret": "<secret>",
"bearer_only": false,
"redirect_uri": "https://gateway.example.com/orders/callback",
"logout_path": "/orders/logout",
"post_logout_redirect_uri": "https://gateway.example.com/",
"session": {
"secret": "<secret>"
}
}

What you will see​

  • A request to the Logout path is answered 302 to the provider's sign-out page, with post_logout_redirect_uri telling the provider where to send the browser afterwards.

Things to know​

  • Redirect URI — Its path has to be one this route serves: the provider sends the browser there with the sign-in code, and only this route finishes the sign-in. The same address is registered at the provider.
  • The people it signs in are not consumers: no consumer or credential is created for them, the backend receives no X-Consumer-Username, and a plugin that lets only named consumers through answers 401 because it finds none. The backend tells people apart by X-Userinfo; Authentication explains consumers.
  • Set raw ID token header — Off by default, so the ID token as the provider signed it does not reach the backend: X-ID-Token carries only its claims, as Base64 JSON. On, the signed token is added as X-Raw-ID-Token.
  • Logout path — Compared with the whole request path, so it has to be one this route serves as well: the default /logout is not under /orders/*. With Bearer only off, a request to it is sent to the provider’s sign-out page.
  • Bearer only — On, the Authorization header reaches the backend as the client sent it, beside X-Access-Token; nothing here strips it.
  • Client ID — With Bearer only on, a token the provider signed passes whichever of its clients it was issued to: Client ID does not narrow the tokens, Required scopes does.
  • Authorization params — Each value is a string or a number; any other shape is refused when you save.
  • Private key — Turning DPoP on asks for this key and the public JWK together. A key left here while DPoP is off is kept as it is.
  • Public JWK — Paste the public half of the key pair only. A JWK that still carries one of its private members (d, p, q, dp, dq, qi, oth, k) is refused.
  • On a consumer or a consumer group it runs only after the consumer has been identified by a credential; it does not identify the consumer itself.

Settings​

The fields as the console shows them, section by section. A field under “When … is …” is there only while that value is set.

Basics​

DiscoverydiscoverystringRequired
example https://idp.example.com/.well-known/openid-configuration
Client IDclient_idstringRequired
Client secretclient_secretstringRequired unless Bearer only is true and Public key is set, or unless Bearer only is true and Use JWKS is true, or unless Bearer only is true and Introspection endpoint auth method is private_key_jwt, or unless Bearer only is false and Token endpoint auth method is private_key_jwt, or unless Bearer only is false and Use PKCE is true

Stored on the engine and shown only to roles that manage this object.

Bearer onlybearer_onlybooleandefault false

When Bearer only is false​

Redirect URIredirect_uristring

The address the identity provider sends the browser back to; its path has to be one this route serves, such as /orders/callback under a route for /orders/*. It is registered at the provider. Left empty, the gateway derives one from each request's own path, so the provider would need to accept a different address for every path.

example https://gateway.example.com/orders/callback
Scopescopestringdefault openid
SessionsessionobjectRequired
Secretsession.secretstringRequired

The key that encrypts and signs the session cookie. Use the same value on every node. A different value per node ends a session when a request lands on another node. Stored on the engine and shown only to roles that manage this object.

at least 16 characters
Absolute timeoutsession.absolute_timeoutinteger
in seconds
Cookie domainsession.cookie_domainstring

The domain the session cookie is sent to. Empty keeps it to the host that set it.

Cookie HTTP onlysession.cookie_http_onlyboolean

Keeps the session cookie out of reach of scripts in the page.

Cookie namesession.cookie_namestring
Cookie pathsession.cookie_pathstring
Cookie same sitesession.cookie_same_sitestring

When the browser sends the session cookie on requests that come from other sites.

one of Default, Lax, None, Strict
Cookie securesession.cookie_secureboolean

Sends the session cookie over HTTPS only.

Idling timeoutsession.idling_timeoutinteger
in seconds
Rolling timeoutsession.rolling_timeoutinteger
in seconds
Storagesession.storagestringdefault cookie
one of cookie, redis

When Storage is redis​

Redissession.redisobjectRequired
Connect timeoutsession.redis.connect_timeoutintegerdefault 1000
at least 1 · in milliseconds
Databasesession.redis.databaseintegerdefault 0

The Redis database number the sessions are stored in.

at least 0
Hostsession.redis.hoststringdefault 127.0.0.1
at least 2 characters
Keepalive timeoutsession.redis.keepalive_timeoutintegerdefault 10000
at least 1000 · in milliseconds
Passwordsession.redis.passwordstring

Stored on the engine and shown only to roles that manage this object.

Portsession.redis.portintegerdefault 6379
at least 1
Prefixsession.redis.prefixstringdefault sessions

Text put in front of every session key in Redis.

Read timeoutsession.redis.read_timeoutintegerdefault 1000
at least 1 · in milliseconds
Send timeoutsession.redis.send_timeoutintegerdefault 1000
at least 1 · in milliseconds
Server namesession.redis.server_namestring

The name sent to Redis when the encrypted connection is opened, for a server that answers to several names.

SSLsession.redis.sslbooleandefault false

Encrypts the connection to Redis.

SSL verifysession.redis.ssl_verifybooleandefault true

Checks the Redis certificate when the connection is encrypted.

Usernamesession.redis.usernamestring
at least 1 character

Behavior​

Unauth actionunauth_actionstringdefault auth

What an unauthenticated request gets: auth sends the browser to the identity provider, deny answers 401, pass lets the request through. Read only for the browser sign-in: with Bearer only on, a request without a token is answered 401 whatever this says.

one of auth, deny, pass
Use PKCEuse_pkcebooleandefault false

Adds a proof key to the authorization flow, as public clients require.

Logout pathlogout_pathstringdefault /logout
Post logout redirect URIpost_logout_redirect_uristring

Where the browser is sent after the logout path is called.

Use JWKSuse_jwksbooleandefault false

Verifies bearer tokens with the provider's published keys instead of asking the provider about each one. Not read when a public key is set.

Introspection endpointintrospection_endpointstring
Required scopesrequired_scopesarray of strings

Scopes the access token must carry, such as payments. A token missing any of them is refused with 403.

Claim validatorclaim_validatorobject
Audienceclaim_validator.audienceobject

Checks the audience claim of the token.

Claimclaim_validator.audience.claimstringdefault aud

The claim that carries the audience.

Match with client IDclaim_validator.audience.match_with_client_idbooleandefault false

Requires the audience to be, or to include, this plugin's client ID.

Requiredclaim_validator.audience.requiredbooleandefault false

Refuses a token without an audience claim.

Issuerclaim_validator.issuerobject

The issuers whose tokens are accepted. Empty takes the issuer from the discovery document. Without either, the issuer is not checked.

Valid issuersclaim_validator.issuer.valid_issuersarray of strings
Set userinfo headerset_userinfo_headerbooleandefault true

Adds the user info to the request as the X-Userinfo header for the upstream.

Set access token headerset_access_token_headerbooleandefault true

Adds the access token to the request as a header for the upstream.

Access token in authorization headeraccess_token_in_authorization_headerbooleandefault false

Sends the access token in the Authorization header instead of X-Access-Token.

Set ID token headerset_id_token_headerbooleandefault true

Adds the ID token to the request as the X-ID-Token header for the upstream.

Advanced​

Use nonceuse_noncebooleandefault false

Adds a one-time value to the authorization request and checks it in the token.

Authorization paramsauthorization_paramsobject

Extra parameters added to the authorization request. An object whose keys are any name, each with a string or number as its value.

example {"prompt":"login","ui_locales":"en"}
Renew access token on expiryrenew_access_token_on_expirybooleandefault true

Renews an expired access token in the background instead of sending the user to sign in again.

Access token expires inaccess_token_expires_ininteger

Lifetime given to an access token whose provider sends no expires_in.

in seconds
Access token expires leewayaccess_token_expires_leewayintegerdefault 0

How long before its expiry an access token is renewed.

in seconds
Refresh session intervalrefresh_session_intervalinteger

How often the ID token is refreshed without a new sign-in.

in seconds
Force reauthorizeforce_reauthorizebooleandefault false

Runs the authorization flow again even when a token is already cached.

Revoke tokens on logoutrevoke_tokens_on_logoutbooleandefault false

Tells the identity provider that the tokens are no longer needed when the user logs out.

Accept unsupported algaccept_unsupported_algbooleandefault true

Accepts an ID token signed with an algorithm the plugin cannot verify, skipping the signature check.

JWT verification cache ignorejwt_verification_cache_ignorebooleandefault false

Verifies every token again instead of trusting a cached verification.

Public keypublic_keystring
Introspection intervalintrospection_intervalintegerdefault 0

The longest an introspected token is cached. Zero caches it until the expiry the introspection answer gives.

in seconds
Introspection expiry claimintrospection_expiry_claimstring

The claim in the introspection answer that says how long the result may be cached.

Introspection addon headersintrospection_addon_headersarray of strings

Request headers copied into the introspection call, one per entry.

at least 1 item
Token signing alg values expectedtoken_signing_alg_values_expectedstring
JWK expires injwk_expires_inintegerdefault 86400

How long the JWK set is cached.

in seconds
Issued-at slackiat_slackintegerdefault 120

Clock skew tolerated on the iat claim of an ID token.

in seconds
Claim schemaclaim_schemaobject

A JSON schema the claims of the token have to satisfy.

example {"type":"object","required":["sub"],"properties":{"sub":{"type":"string"}}}
Accept the none algorithmaccept_none_algbooleandefault false

Accepts an unsigned ID token, for a provider that does not sign its tokens.

Cache segmentcache_segmentstring

A name that keeps this plugin's cache apart from the caches of other routes.

Realmrealmstringdefault apisix
Token endpoint auth methodtoken_endpoint_auth_methodstringdefault client_secret_basic
Introspection endpoint auth methodintrospection_endpoint_auth_methodstringdefault client_secret_basic
Client RSA private keyclient_rsa_private_keystring

The private key the client assertion is signed with, for a provider that authenticates the client with a signed token. Sent to the engine and never shown again.

Client RSA private key IDclient_rsa_private_key_idstring

The key ID sent with the client assertion so the provider can pick the matching public key.

Client JWT assertion algclient_jwt_assertion_algstring

The algorithm the client assertion is signed with.

one of ES256, ES512, HS256, HS512, RS256, RS512
Client JWT assertion audienceclient_jwt_assertion_audiencestring

The audience written into the client assertion, usually the provider's token endpoint.

Client JWT assertion expires inclient_jwt_assertion_expires_inintegerdefault 60

Life of the signed JWT the client presents.

in seconds
Set raw ID token headerset_raw_id_token_headerbooleandefault false

Adds the ID token as it was signed to the request as the X-Raw-ID-Token header.

Set refresh token headerset_refresh_token_headerbooleandefault false

Adds the refresh token to the request as the X-Refresh-Token header for the upstream.

PARparobject

Sends the authorization request to the provider directly and hands the browser only a reference to it.

Pushed authorization requestspar.enabledbooleandefault false
Endpointpar.endpointstring

The provider's endpoint the authorization request is pushed to.

Endpoint auth methodpar.endpoint_auth_methodstring

How the plugin authenticates itself at the pushed authorization endpoint.

one of client_secret_basic, client_secret_jwt, client_secret_post, private_key_jwt
DPoPdpopobject

Binds the tokens to a key this plugin holds, so a stolen token cannot be used elsewhere.

DPoP proof JWTsdpop.enabledbooleandefault false
Private keydpop.private_keystringRequired when DPoP proof JWTs is true

The key the proof tokens are signed with. Sent to the engine and never shown again.

Public JWKdpop.public_jwkobjectRequired when DPoP proof JWTs is true

The public half of the proof key, as a JSON Web Key.

example {"kty":"EC","crv":"P-256","x":"f83OJ3D2xF1Bg8vub9tLe1gHMzV76e8Tus9uPHvRVEU","y":"x_FEzRu9m36HLN_tue659LNpXW6pCyStikYjKIWI5a0"}
Signing algdpop.signing_algstringdefault ES256

The algorithm the proof tokens are signed with.

one of ES256, PS256, RS256
SSL verifyssl_verifybooleandefault true
Timeouttimeoutintegerdefault 3

How long a call to the identity provider may take. A value of 1000 or more that divides by 1000 is read in the older, thousand-times-finer form: 2000 means 2. A provider that does not answer holds each request for twice this value.

at least 1 · in seconds
Proxy optionsproxy_optsobject

A proxy the identity provider is reached through.

HTTP proxyproxy_opts.http_proxystring

The proxy for plain HTTP calls to the provider, as an address with a port.

HTTP proxy authorizationproxy_opts.http_proxy_authorizationstring

The Proxy-Authorization value for the HTTP proxy, such as a Basic credential.

HTTPS proxyproxy_opts.https_proxystring

The proxy for HTTPS calls to the provider, as an address with a port.

HTTPS proxy authorizationproxy_opts.https_proxy_authorizationstring

The Proxy-Authorization value for the HTTPS proxy, such as a Basic credential.

No proxyproxy_opts.no_proxystring

Hosts reached directly, not through the proxy, separated by commas.

Every plugin also has Enabled, Priority, Filter, Error response and Pre-function: see Plugin execution settings.

G8 dev · Apache APISIX® 3.19.0
Apache, Apache APISIX and APISIX are trademarks of The Apache Software Foundation.
Facts
Category
Authentication
Add it to
Route, Service, Consumer, Consumer group, Global rule, Plugin config
Engine plugin
openid-connect
Priority
2599