Skip to content

feat(auth): accept IdP-issued OIDC bearer tokens on the API#35

Merged
AshDevFr merged 3 commits into
mainfrom
idp-bearer-token
Jun 12, 2026
Merged

feat(auth): accept IdP-issued OIDC bearer tokens on the API#35
AshDevFr merged 3 commits into
mainfrom
idp-bearer-token

Conversation

@AshDevFr

Copy link
Copy Markdown
Owner

Summary

The Codex API now accepts access tokens issued by a configured OIDC identity provider (e.g. Authentik) as Authorization: Bearer credentials, in addition to Codex's own session tokens. A valid IdP token is resolved to the local user who previously linked that identity by signing into Codex web via SSO. This makes Codex a proper OAuth2 resource server on top of its existing OIDC login support.

Motivation

Until now, the API bearer path only accepted Codex's own session tokens, so any IdP-issued token was rejected with "Invalid JWT token". This broke every downstream system that forwards a caller's IdP token to Codex, concretely Shisho's passthrough authentication (both its web SSO sign-in and the claude.ai MCP connector fail on the Codex /api/v1/auth/me check). Codex is the identity authority for that ecosystem, so the resource-server half belongs here rather than in downstream workarounds.

Changes

  • API: Authorization: Bearer now accepts tokens issued by any configured OIDC provider. The token's issuer, audience, signature, and expiry are verified against the provider, and signing-key rotation at the IdP is picked up automatically without a restart.
  • API: a validated identity must already be linked to a Codex account (by signing into Codex web via SSO once); valid tokens for unlinked identities get a 401 telling the user to link first. There is no auto-provisioning of accounts from bearer tokens.
  • API: rejected tokens return a 401 with the precise reason logged server-side (wrong issuer, wrong audience, expired, bad signature) without ever echoing token material; IdP outages surface as 503 rather than 401 so they don't read as bad credentials.
  • Operators / config: new optional per-provider accepted_audiences list controls which audiences are accepted on API bearer tokens, defaulting to the provider's client_id; settable via config file or the matching ..._ACCEPTED_AUDIENCES environment variable. Shipped example configs include a commented sample.
  • Docs: the OIDC documentation gains an "API Bearer Tokens (Resource Server)" section covering setup, the account-linking requirement, audience rules, and a troubleshooting table mapping logged rejection reasons to fixes.

Notes

  • Fully config-gated: with OIDC disabled or no providers configured, behavior is byte-for-byte unchanged. Existing session tokens, API keys, and Basic auth are unaffected in all configurations.
  • No database migration; rollback is just removing the config.
  • Role/group sync still happens only at web login: a bearer token's group claims are ignored, so IdP role changes propagate on the user's next web sign-in.
  • Auto-provisioning from bearer tokens and a token-exchange endpoint are deliberately out of scope, noted as future enhancements.

AshDevFr added 3 commits June 11, 2026 16:23
…audiences

Groundwork for accepting OIDC provider-issued access tokens as API
bearer credentials (resource-server support), so downstream apps like
Shisho can forward their users' IdP tokens to Codex.

- Add `accepted_audiences` to OidcProviderConfig: audiences accepted on
  API bearer tokens from a provider, defaulting to the provider's own
  client_id. Configurable via YAML or the ..._ACCEPTED_AUDIENCES env
  override (comma-separated).
- New codex_services::idp_bearer::IdpBearerValidator: validates RS256/
  ES256 bearer JWTs against the matching provider's JWKS, selected by an
  unverified `iss` peek and re-verified during validation. Enforces
  issuer (trailing-slash tolerant), audience against the accepted list,
  and exp/nbf with 30s clock-skew leeway. `iss`/`aud` are required
  claims so tokens missing them fail closed.
- HS256 and alg=none are rejected outright on this path; symmetric
  algorithms stay reserved for Codex's local session JWTs, closing the
  key-confusion attack.
- Per-provider JWKS cache: lazy fetch from the discovery document, 1h
  refresh, stale-serve on refresh failure, and a rate-limited refetch on
  unknown `kid` so key rotation works without a restart. HTTP client has
  redirects disabled and a 10s timeout.
- Error enum distinguishes every rejection cause for precise auth logs
  (never echoing token material) and separates IdP outages from token
  rejections.

Validation is not wired into the auth extractor yet; this is the
self-contained service layer. Unit and integration tests added, the
latter against a local axum IdP stub with static RSA test fixtures.
Wire the IdP bearer validator into the auth extractor, making Codex a
proper OAuth2 resource server: provider-issued RS256/ES256 access
tokens now authenticate API requests for users who have linked that
identity via web SSO. This unblocks downstream services (e.g. Shisho's
passthrough strategy) that forward their callers' IdP tokens to Codex.

- Route bearer tokens by JWT header algorithm in extract_from_jwt:
  HS256 session tokens keep the existing local-secret path verbatim;
  RS256/ES256 divert to the IdP validator only when one is configured.
  Without a validator (OIDC disabled or no providers), behavior is
  unchanged, including error messages.
- Resolve validated (provider, subject) identities through
  oidc_connections; no auto-provisioning. An unlinked identity gets a
  401 telling the user to sign into Codex via SSO once. Inactive users
  are rejected identically to the local path.
- Add AppState.idp_bearer (IdpBearerAuth): the validator bundled with a
  TTL'd (provider, subject) -> user_id cache so hot endpoints don't hit
  the connection table per request; user data freshness stays governed
  by the existing user auth cache, which both paths now share through a
  common resolve_user_context helper.
- Add AuthMethod::OidcBearer for audit visibility.
- Token rejections return a uniform 401 with the precise cause logged
  (never echoing token material); IdP-side failures (discovery/JWKS
  unreachable) return 503 instead of 401 so outages don't read as bad
  credentials.

Covers both AuthContext and FlexibleAuthContext extractors. Unit tests
for the subject cache and dispatch helper, plus API integration tests
against a local IdP stub (linked/unlinked/tampered/inactive/disabled
and HS256/Basic-auth regression with the validator configured).
Operator-facing documentation for the resource-server support added in
the previous commits, so the feature is configurable without reading
source.

- New "API Bearer Tokens (Resource Server)" section in the OIDC docs:
  how validation works (algorithm allowlist, issuer-based provider
  selection, JWKS verification with automatic key-rotation pickup,
  audience and expiry enforcement), the sign-in-via-SSO-once linking
  requirement and why there is no auto-provisioning, and audience rules
  with a worked accepted_audiences example.
- Troubleshooting table mapping each server-logged rejection reason to
  its likely cause and fix, including the 503 IdP-unreachable case.
- accepted_audiences added to the provider settings reference and the
  environment variable override examples.
- Commented accepted_audiences examples in the shipped sqlite, docker,
  and kubernetes config templates.
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying codex with  Cloudflare Pages  Cloudflare Pages

Latest commit: 71e4802
Status: ✅  Deploy successful!
Preview URL: https://992e9091.codex-asm.pages.dev
Branch Preview URL: https://idp-bearer-token.codex-asm.pages.dev

View logs

@AshDevFr AshDevFr merged commit 4bc0b26 into main Jun 12, 2026
22 checks passed
@AshDevFr AshDevFr deleted the idp-bearer-token branch June 12, 2026 01:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant