Harden OIDC token endpoints from security review
Fixes from review of the device flow / introspection / DCR work:
Introspection over-disclosure (RFC 7662):
- Restrict introspection to authorized callers — the client a token was issued
to, or a resource server registered (resource_identifiers) to serve the token's
bound RFC 8707 audience. Unauthorized callers get {active:false}, disclosing
nothing, instead of any confidential client reading any token.
- Scope-gate disclosed claims: username only with the email scope, groups only
with the groups scope (mirrors userinfo). ADR 0005.
Tokens minted for revoked users:
- Re-check application.user_allowed?(user) at mint time in the device, refresh,
and authorization-code grants (covers app-active, user-active, group
membership). A user deactivated or removed from the allowed group between
approval and the token request is refused with access_denied. The refresh
check runs before rotation so a denied refresh has no side effects.
Device authorization hardening:
- Require confidential clients to authenticate, and require PKCE (code_challenge)
up front for clients that require it, so an intercepted device_code plus a
known public client_id cannot redeem tokens.
- Merge (not overwrite) the shared consent record on device approval.
Tests: new oidc_introspection_test and oidc_mint_authorization_test; existing
PKCE/claims tests updated to grant app access (they created ungrouped apps and
relied on the token endpoint not checking authorization). Full suite green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01F7cwhwDJp3MJJDoNPVE6zq
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
2defa26a87
commit
017dfdff0e
@@ -0,0 +1,46 @@
|
||||
# 0005 — Introspection authorization & claim scope-gating
|
||||
|
||||
**Status:** Accepted · **Date:** 2026-07-19
|
||||
|
||||
## Decision
|
||||
|
||||
The RFC 7662 introspection endpoint restricts *which* tokens a caller may see and
|
||||
*which* claims it returns:
|
||||
|
||||
1. **Authorization to introspect.** A caller may introspect a token only if it was
|
||||
issued to that caller, or the token is bound (RFC 8707 `resource`) to a resource
|
||||
the caller is registered to serve (`Application#serves_resource?`, backed by a new
|
||||
`resource_identifiers` column). Unauthorized callers get the same `{active:false}`
|
||||
as an unknown token — disclosing nothing.
|
||||
2. **Claim scope-gating.** `username` (email) is returned only when the token carries
|
||||
the `email` scope; `groups` only with the `groups` scope — mirroring the userinfo
|
||||
endpoint. `sub` is a pairwise pseudonym and is always safe to return.
|
||||
|
||||
## Context
|
||||
|
||||
The first cut authenticated the caller (any confidential client) but then returned
|
||||
`active:true` plus the user's email and **all** group names for **any** token — even
|
||||
tokens issued to a different client and regardless of the token's scopes. A
|
||||
low-privilege second client could therefore harvest every user's email and group
|
||||
memberships by replaying tokens it observed. RFC 7662 §4 explicitly calls for the AS
|
||||
to verify the resource server is authorized to introspect the particular token,
|
||||
typically via audience restriction.
|
||||
|
||||
## How it fits together
|
||||
|
||||
This is the enforcement half of the RFC 8707 resource indicators
|
||||
([0004](0004-resource-indicators.md)): the CLI/agent requests a token with
|
||||
`resource=<resource server>`, the resource server is registered with that same
|
||||
identifier, and only it can introspect (and thereby read the user's groups to
|
||||
authorize). A token minted for one resource server cannot be introspected by another.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **c2a2 setup:** the `c2a2-introspection` client must declare its
|
||||
`resource_identifiers` (seeded from `C2A2_RESOURCE`), and the CLI must request its
|
||||
token with `resource=<that URL>`. A token with no bound resource can only be
|
||||
introspected by the client it was issued to.
|
||||
- Resource servers see only the identity claims the token was actually granted, so an
|
||||
over-broad token (or a misconfigured scope) can't leak email/groups.
|
||||
- Unauthorized introspection is indistinguishable from an unknown token, preventing
|
||||
token-scanning and cross-client identity harvesting.
|
||||
Reference in New Issue
Block a user