Skip to content

feat(openid-connect): introspect the session-held access token - #13792

Open
janiussyafiq wants to merge 2 commits into
apache:masterfrom
janiussyafiq:feat-oidc-session-introspection
Open

feat(openid-connect): introspect the session-held access token#13792
janiussyafiq wants to merge 2 commits into
apache:masterfrom
janiussyafiq:feat-oidc-session-introspection

Conversation

@janiussyafiq

Copy link
Copy Markdown
Contributor

Description

In the authorization code flow the tokens live in the session cookie, and nothing revisits the identity provider until the stored token expiry passes: a token revoked at the provider (logout via another application, administrative session termination) keeps being accepted and forwarded upstream until it expires.

This adds an opt-in introspect_session_access_token option that introspects (RFC 7662) the access token held in the session on every request:

  • An inactive verdict destroys the session and the request follows unauth_action. The negative verdict is cached for 10 seconds so replays of a discarded session cookie do not flood the introspection endpoint. The TTL is deliberately a fixed constant rather than a schema field: it has no user-facing semantic to tune, and reusing introspection_interval would both disable the protection at its default 0 and conflate it with how long a positive verdict may be trusted. It can be promoted to a schema field later if a need appears.
  • No verdict (introspection endpoint unreachable or missing) returns 503 and keeps the session — deliberately fail-closed, also under unauth_action=pass, so a possibly revoked token never reaches the upstream while a provider hiccup does not log the whole user base out.
  • Active verdicts are cached only when introspection_interval > 0; the default introspects every request, since a cached "active" delays revocation detection by its TTL.

Not allowed together with bearer_only (rejected by the schema): header-presented tokens are already introspected; only the session path lacked revocation checking. lua-resty-openidc deliberately leaves this to the caller (see zmartzone/lua-resty-openidc#412) and exposes the pieces used here (call_token_endpoint, get_discovery_doc). OIDC Back-Channel Logout would be the complementary mechanism, but it requires server-side session storage indexed by sid, so it is out of scope here.

Which issue(s) this PR fixes:

Fixes #13750

Checklist

  • I have explained the need for this PR and the problem it solves
  • I have explained the changes or the new features added to this PR
  • I have added tests corresponding to this change
  • I have updated the documentation to reflect this change
  • I have verified that this change is backward compatible (If not, please discuss on the APISIX mailing list first)

The authorization code flow stores tokens in the session cookie and
nothing revisits the identity provider until the stored expiry passes,
so a token revoked at the provider keeps being forwarded upstream.

Add an opt-in introspect_session_access_token option: an inactive
verdict destroys the session and follows unauth_action (negative
verdicts cached 10s against cookie replays), a transport failure
returns 503 while keeping the session, and active verdicts are cached
only when introspection_interval > 0.

Fixes apache#13750
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. enhancement New feature or request labels Aug 7, 2026
…-introspection

# Conflicts:
#	t/plugin/openid-connect.t
@nic-6443

nic-6443 commented Aug 7, 2026

Copy link
Copy Markdown
Member

I went looking at how other OIDC-capable gateways and proxies handle revocation of a session-held token, since it seemed worth knowing what the prior art looks like. Sharing the survey — the short version is that none of the browser-session implementations introspect the session token per request. They either wait for a back-channel logout notification, or re-validate periodically through the refresh grant.

Project Session storage Per-request introspection of session token Back-channel logout Actual revocation mechanism
mod_auth_openidc server-cache (default) / cookie No — introspection lives entirely under OIDCOAuth*, the RS path Yes, with jti replay cache back-channel logout + OIDCRefreshAccessTokenBeforeExpiry
Traefik Hub cookie / Redis No Yes (backchannelLogoutUrl) back-channel logout
traefikoidc (community plugin) cookie / Redis No — introspection is bearer-only by design Yes (enableBackchannelLogout) back-channel logout, Redis required for multi-replica
Spring Security HttpSession No Yes, since 6.2 (OidcBackChannelLogoutHandler in 6.4) back-channel logout
Kong OIDC cookie / Redis / memcache No — reverify is local JWT verification only No server-side session + revoke on logout
oauth2-proxy cookie / Redis No No, requested since 2021 --cookie-refresh periodic refresh grant
Envoy OAuth2 filter HMAC cookie No No, open issue RP-initiated logout only
NGINX Plus nginx-openid-connect keyval (server-side) No No keyval + /logout deletes the key
Pomerium databroker (fully server-side) No No log out when refresh fails + admin revocation
Gloo Gateway cookie / Redis No — introspectionUrl applies to header tokens No Redis session + logoutPath
Ory Oathkeeper none Yes, per request by default N/A introspection

Two notes on reading the table. Oathkeeper is the only per-request introspector, and it has no session concept at all — it handles pure bearer traffic, which is the path introspect() already covers here.

And the refresh-based mechanism in the last column: mod_auth_openidc exposes it as OIDCRefreshAccessTokenBeforeExpiry <seconds> [logout_on_error | authenticate_on_error | 502_on_error], refreshing the token ahead of expiry and treating a failed refresh, meaning the OP revoked the refresh token, as a logout. --cookie-refresh in oauth2-proxy and Pomerium's "log out when refresh fails" are the same idea — it needs no introspection support from the OP, and the detection delay is bounded by the refresh interval.

The mod_auth_openidc row may be the most directly comparable, being the same author as lua-resty-openidc: every introspection directive there is namespaced OIDCOAuth*, i.e. the OAuth 2.0 Resource Server path, and the session path has no introspection option at all.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: openid-connect plugin does not refresh access token when end server responds with 401

2 participants