mirror of
https://github.com/better-auth/better-auth.git
synced 2026-07-26 02:16:23 -05:00
docs(oauth-provider): reframe back-channel logout changeset and fill doc gaps (#9916)
This commit is contained in:
@@ -3,15 +3,17 @@
|
||||
"better-auth": patch
|
||||
---
|
||||
|
||||
Add OIDC Back-Channel Logout 1.0 support to `@better-auth/oauth-provider`.
|
||||
Propagate sign-out to every connected app and cut off API access immediately, via OIDC Back-Channel Logout 1.0.
|
||||
|
||||
When a user's OP session ends (sign-out, `/oauth2/end-session`, admin revoke, etc.), the provider now enumerates OAuth clients with tokens bound to the session, signs a `logout+jwt` Logout Token per client, and POSTs it to each client's registered `backchannel_logout_uri` in parallel with a short per-RP timeout. Clients can opt in by registering `backchannel_logout_uri` (and optionally `backchannel_logout_session_required`) via DCR or the admin client-create endpoint.
|
||||
When a user's session ends at the OP (sign-out, `/oauth2/end-session`, admin revoke, ban), `@better-auth/oauth-provider` now notifies every Relying Party that holds tokens for that session. The user's API access is cut off right away, instead of access tokens staying usable until their own TTL. Each client opts in by registering a `backchannel_logout_uri` (and optionally `backchannel_logout_session_required`) via DCR or the admin client-create endpoint. The provider signs a `logout+jwt` Logout Token per client and POSTs it to that client in parallel, with a short per-RP timeout.
|
||||
|
||||
**Breaking change.** Introspection of an opaque or JWT access token whose bound session has ended now returns `{ active: false }`, and `/oauth2/userinfo` rejects it with `invalid_token`. Previously the token stayed active until its own TTL. If you relied on access tokens outliving the user's session, that no longer holds.
|
||||
|
||||
Refresh tokens without `offline_access` are revoked on session end; `offline_access` refresh tokens are preserved so long-lived API access can survive the browser session (OIDC Back-Channel Logout 1.0 §2.7). Access-token invalidation on session end is an additional OP hardening choice beyond §2.7, enforced by session liveness, so it holds even when the JWT plugin is disabled.
|
||||
|
||||
Delivery runs through the host's background task handler when one is configured (Vercel `waitUntil`, Cloudflare `ctx.waitUntil`); without a handler it completes inline so notifications are not lost on request teardown. Configure `advanced.backgroundTasks.handler` on serverless runtimes to keep sign-out fast.
|
||||
|
||||
Discovery documents at `/.well-known/openid-configuration` and `/.well-known/oauth-authorization-server` now advertise `backchannel_logout_supported: true` and `backchannel_logout_session_supported: true` when the JWT plugin is enabled.
|
||||
|
||||
On session end, refresh tokens without `offline_access` are revoked and refresh tokens with `offline_access` are preserved, per OIDC Back-Channel Logout 1.0 §2.7. As additional hardening (beyond §2.7, which only addresses refresh tokens), access tokens bound to the session are revoked too. Introspection and `/oauth2/userinfo` now treat an opaque or JWT access token whose session has ended as inactive: introspection returns `{ active: false }` and userinfo returns `invalid_token`. This is a breaking change vs. prior behavior, where such tokens stayed active until their own TTL.
|
||||
Discovery at `/.well-known/openid-configuration` and `/.well-known/oauth-authorization-server` advertises `backchannel_logout_supported: true` and `backchannel_logout_session_supported: true` when the JWT plugin is enabled. Registering a `backchannel_logout_uri` rejects fragments, non-http(s) schemes, and non-HTTPS targets on confidential clients. Its SSRF host guard, which blocks private, reserved, tunneled, and cloud-metadata hosts, now also covers a `private_key_jwt` client's `jwks_uri`.
|
||||
|
||||
Schema changes on `@better-auth/oauth-provider`:
|
||||
|
||||
@@ -19,4 +21,4 @@ Schema changes on `@better-auth/oauth-provider`:
|
||||
- `oauthClient.backchannelLogoutSessionRequired: boolean`
|
||||
- `oauthAccessToken.revoked: Date | null`
|
||||
|
||||
`better-auth`'s `signJWT` gains an optional `header` argument so JWT profiles that require an explicit media type (e.g. `typ: "logout+jwt"`) can be expressed without reaching for the low-level signing primitives.
|
||||
`better-auth`'s `signJWT` gains an optional `header` argument, forwarded to custom remote signers. JWT profiles that need an explicit media type, such as `typ: "logout+jwt"`, can now set it without reaching for the low-level signing primitives.
|
||||
|
||||
@@ -681,6 +681,8 @@ await auth.api.adminCreateOAuthClient({
|
||||
|
||||
When `backchannel_logout_session_required` is `true`, the RP requires a `sid` claim in every Logout Token. Every Logout Token the OP sends already includes `sid`, so such clients are always served.
|
||||
|
||||
The `backchannel_logout_uri` is validated at registration. It must be an absolute URL with no fragment, and must use `https` for confidential clients (loopback `http` such as `http://127.0.0.1:<port>` is allowed for local development). It is also rejected when its host is private, reserved, tunneled (NAT64, 6to4, IPv4-mapped IPv6), or a cloud-metadata name. This host check is syntactic: it does not resolve DNS, so pin or re-check the resolved address if you need protection against DNS rebinding. The same host check guards a `private_key_jwt` client's `jwks_uri`, which separately requires `https` unconditionally and a trusted origin. A URI that fails validation is rejected with `invalid_client_metadata`.
|
||||
|
||||
The OP enumerates clients with active tokens bound to the ending session and POSTs one `logout_token` to each in parallel (5s per-RP timeout, no retry per spec §2.5). It then revokes the session's tokens:
|
||||
|
||||
* **Refresh tokens** without `offline_access` are revoked; those with `offline_access` are preserved so long-lived API access can survive the browser session (spec §2.7).
|
||||
@@ -706,7 +708,7 @@ The Logout Token carries the §2.4 claims (`iss`, `aud`, `iat`, `exp`, `jti`, `e
|
||||
|
||||
### UserInfo Endpoint
|
||||
|
||||
The UserInfo Endpoint provides [OIDC](https://openid.net/specs/openid-connect-core-1_0.html)-compliant user information. Available at `/oauth2/userinfo`, the endpoint requires a valid access token with at least the scope `openid`.
|
||||
The UserInfo Endpoint provides [OIDC](https://openid.net/specs/openid-connect-core-1_0.html)-compliant user information. Available at `/oauth2/userinfo`, the endpoint requires a valid access token with at least the scope `openid`. An access token that is expired, revoked, or bound to a session that has ended is rejected with `invalid_token` (401), per [RFC 6750](https://datatracker.ietf.org/doc/html/rfc6750#section-3.1).
|
||||
|
||||
```ts
|
||||
// Example of how a client would use the UserInfo endpoint
|
||||
|
||||
Reference in New Issue
Block a user