feat(openid-connect): support OIDC Back-Channel Logout 1.0 - #13808
Open
janiussyafiq wants to merge 15 commits into
Open
feat(openid-connect): support OIDC Back-Channel Logout 1.0#13808janiussyafiq wants to merge 15 commits into
janiussyafiq wants to merge 15 commits into
Conversation
Member
|
Just approved the CI to run, thanks |
…l endpoint The back-channel logout endpoint is unauthenticated and attacker-postable. It reused the route's accept_none_alg/public_key options, which are meant for the browser-facing ID-token path, so a route that enabled them would also accept forged unsigned logout tokens. Force back-channel logout token verification to always require a real JWKS-verified signature.
…el logout Session revocation is keyed off the id_token's sid/sub claims. If an operator restricts session_contents without keeping id_token, lua-resty-openidc does not store those claims, so every request silently skips the denylist while the endpoint still returns 200 to the identity provider. Reject that combination at config time.
…on-positive session timeout session.absolute_timeout of 0 (or negative) is lua-resty-session's "no absolute limit" sentinel. Because 0 is truthy in Lua, the previous `or` fallback passed it straight through as the denylist TTL, so redis SET ... EX 0 failed the write and every logout delivery returned 400. Fall back to the default TTL unless the timeout is a positive number.
…logout schema The back-channel logout redis store connects through apisix/utils/redis.lua, which has no server_name (SNI) support and applies one timeout to connect, send and read. The schema still advertised server_name, send_timeout and read_timeout "same as session.redis", so those settings validated but silently did nothing. Remove them from the back-channel logout redis schema and make the docs honest; session.redis itself is unchanged.
…ore the token iat Replace the check-then-set jti replay guard with an atomic add-if-absent (SET NX / safe_add) so two concurrent deliveries of the same logout token cannot both pass. Store the logout token's iat as the denylist value instead of the receipt time, so the revocation cutoff is stable and a late duplicate cannot advance it and revoke sessions created after the logout. Also close the redis connection on error paths instead of returning it to the pool, matching the ai-cache convention.
…ck-channel logout RFC 9110 requires a 405 response to advertise the supported methods. The back-channel logout endpoint only accepts POST, so return Allow: POST alongside the 405.
Add the no_error_log default to the block preprocessor so blocks that do not expect an error fail on a stray [error] log, matching the sibling openid-connect tests. Assert HTTP status via ngx.status/--- error_code instead of printing it into the response body, assert the 405 Allow and the Cache-Control headers via --- response_headers, and turn the missing-token check into a direct request.
Factor the repeated RS256 logout-token signing into t/lib/backchannel_logout.lua with defaults for iss/aud/iat/exp/events and an OMIT sentinel, so each test declares only the claims it varies instead of duplicating the whole sign call.
…n back-channel logout Fill the logout-token validation matrix: reject a token whose iss does not match the discovery issuer and one whose iat is too far in the future, and accept a token whose aud is an array that contains the client_id.
nic-6443
reviewed
Aug 13, 2026
Comment on lines
+1201
to
+1250
| local function bcl_redis_connect(rconf) | ||
| local red, err = redis.new({ | ||
| redis_host = rconf.host, | ||
| redis_port = rconf.port, | ||
| redis_username = rconf.username, | ||
| redis_password = rconf.password, | ||
| redis_database = rconf.database, | ||
| redis_ssl = rconf.ssl, | ||
| redis_ssl_verify = rconf.ssl_verify, | ||
| redis_timeout = rconf.connect_timeout, | ||
| }) | ||
| if not red then | ||
| return nil, "failed to connect to redis: " .. err | ||
| end | ||
| return red | ||
| end | ||
|
|
||
|
|
||
| -- One revocation (or seen-jti) entry per key, holding the caller-supplied | ||
| -- value (the logout token's iat, for entries the request path compares | ||
| -- against). | ||
| local function bcl_store_set(conf, key, value, ttl) | ||
| if conf.backchannel_logout.storage == "redis" then | ||
| local rconf = bcl_redis_conf(conf) | ||
| local red, err = bcl_redis_connect(rconf) | ||
| if not red then | ||
| return false, err | ||
| end | ||
| local ok | ||
| ok, err = red:set(rconf.prefix .. ":" .. key, value, "EX", ttl) | ||
| if not ok then | ||
| red:close() | ||
| return false, "failed to write to redis: " .. err | ||
| end | ||
| red:set_keepalive(rconf.keepalive_timeout, 100) | ||
| return true | ||
| end | ||
|
|
||
| local dict = ngx.shared.bcl | ||
| if not dict then | ||
| return false, "shared dict \"bcl\" is missing" | ||
| end | ||
| -- safe_set: evicting an unexpired revocation to make room would silently | ||
| -- re-admit a revoked session, so a full dict must fail the write instead. | ||
| local ok, err = dict:safe_set(key, value, ttl) | ||
| if not ok then | ||
| return false, "failed to write to the shared dict: " .. err | ||
| end | ||
| return true | ||
| end |
Member
There was a problem hiding this comment.
These codes that directly manipulate Redis data break the encapsulation of the OIDC and session libraries. We should not do this and need to find or add relevant features in the upstream libraries.
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Add an OIDC Back-Channel Logout 1.0 receiver to the
openid-connectplugin, following the direction from the prior-art discussion in #13792: when the identity provider notifies the gateway that a user logged out elsewhere or an administrator revoked a session, the corresponding session cookie stops being accepted immediately, instead of remaining valid until its stored token expiry.backchannel_logoutconfig block: the provider POSTs alogout_tokentobackchannel_logout.path; after validation (JWKS signature,iss/aud/iat/events/jticlaim checks per spec section 2.6, jti replay guard) the revokedsid/subis written to a denylist, stored inshm(per-instance) orredis(shared across nodes, reusingsession.rediswhen configured).unauth_action; if the store is unreachable, the request fails with 503 and the session is kept.unauth_actionvariants, store-down behavior) plus a stub-IdP negative validation matrix; docs updated (en/zh).Which issue(s) this PR fixes:
Fixes #13750
Checklist
backchannel_logoutis configured)