Skip to content

feat: verify-only resource server mode with audience, scope, role and step-up guards - #21

Merged
Bccorb merged 1 commit into
mainfrom
feat/verify-only-resource-server
Oct 31, 2026
Merged

Bccorb merged 1 commit into
mainfrom
feat/verify-only-resource-server

Conversation

@Bccorb

@Bccorb Bccorb commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

What

A verify-only mode for a Python API that only receives OpenID Provider access tokens (RFC 9068 JWTs from the API's /oidc/sign) as Authorization: Bearer. It needs the issuer and an audience, and nothing else: no cookie secret, no service secret, no auth routes.

  • ResourceServer(issuer, audience, *, jwks_uri=None, http_client=None, confirm=None, clock=None): OpenID discovery (or a fixed jwks_uri), then RS256 verification with explicit alg, typ (at+jwt), kid, crit, key use/alg, iss, aud, exp/nbf (5s skew), sub, client_id, first-party typ and claim type checks. A cnf token is refused unless the confirm hook accepts it (DPoP readiness, feat: sender-constrained tokens with DPoP (RFC 9449) seamless-auth-api#512).
  • VerifiedUser with scopes, client_id, sid, org_id, acr, amr, auth_time, roles.
  • Guards require_scope, require_role (a port of roleGrantsAccess from @seamless-auth/types) and require_step_up (RFC 9470 insufficient_user_authentication challenge).
  • FastAPI: RequireBearer(server, *guards) and add_bearer_error_handler(app). Django: SEAMLESS_RESOURCE_SERVER settings, @require_bearer with @require_scope, @require_role, @require_step_up beneath it, and bearer_user_of(request).
  • JwksCache now takes the full key set URL and keeps each key's use/alg. The existing adapter passes the same URL it built before and its verification is unchanged.
  • README section for the new mode.

Why

Parity with the JS, Go and Rust adapters for services behind the OpenID Provider (fells-code/seamless-auth-api#387): a FastAPI or Django service configured with only an issuer URL and an audience can require a scope, a role, or a recent passkey on any route.

The status codes, bodies and WWW-Authenticate strings follow the shared verify-only spec. The tests run the shared signed vectors, copied byte for byte from fells-code/seamless-auth-server (packages/core/tests/fixtures/verify-only/vectors.json) into tests/data/verify_only_vectors.json: every verify[] entry against the verifier, and every requests[] entry through FastAPI and through Django (sync and async views). Adapter-local tests cover discovery (issuer mismatch, http jwks_uri under an https issuer, failure is 401 not 500, one discovery per process, 5s retry), construction errors, malformed tokens and the confirmation hook.

The conformance reference apps are unchanged: the conformance contract has no verify-only routes yet.

Closes #16

Checks

  • uv sync --locked
  • uv run ruff check .: All checks passed
  • uv run ruff format --check .: 39 files already formatted
  • uv run mypy: Success, no issues found in 14 source files
  • uv run pytest: 502 passed (230 new)

@Bccorb
Bccorb merged commit f5b58bf into main Oct 31, 2026
6 checks passed
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.

feat: verify-only resource server mode with audience, scope and step-up guards

1 participant