Skip to content

Phase 2: OAuth 2.1 RFC compliance - #13

Open
manish-wekan wants to merge 1 commit into
developfrom
phase-2-oauth-compliance
Open

Phase 2: OAuth 2.1 RFC compliance#13
manish-wekan wants to merge 1 commit into
developfrom
phase-2-oauth-compliance

Conversation

@manish-wekan

@manish-wekan manish-wekan commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Closes the RFC-compliance gaps in nitrostack/auth/oauth.py tracked as Phase 2 of the modernization plan.

Added

  • PKCE (RFC 7636)nitrostack/auth/pkce.py, a standalone, fully-tested utility module (verifier/challenge generation, verification, format validation). Not wired into a local authorization-code exchange endpoint: nitrostack is an OAuth resource server, never an authorization server, in either SDK — it validates incoming tokens but never issues them. The TypeScript reference confirms this scope.
  • RFC 8707 resource-indicator (audience) validation — a token's aud claim is now checked against the configured resource on both validation paths. Previously only the JWKS path enforced this (via PyJWT internally); the introspection-endpoint path had no check at all, meaning a token issued for a different resource would have been silently accepted.
  • Full RFC 8414 discovery document/.well-known/oauth-authorization-server now serves all commonly-required fields instead of 3.
  • Dynamic Client Registration (RFC 7591)nitrostack/auth/oauth_module.py, a POST /oauth/v2/register endpoint. This is the same simplified, static-credential variant the TypeScript SDK ships (opt-in, hands back pre-configured client_id/client_secret, no per-client storage), not full multi-tenant DCR — matches what TS actually does, not the plan file's more elaborate wording.
  • JWKS client caching and token-result caching (TTL-based) — every tool call previously reconstructed the JWKS client and re-verified from scratch; now both are cached, matching the TypeScript SDK's caching strategy.
  • AuthContext.aud — surfaces the audience claim to guard/tool code, normalized to a list.

Changed

  • Introspection-endpoint-before-JWKS precedence now matches TS exactly (previously reversed) — a deployment configuring both no longer gets divergent behavior between the two SDKs.
  • Removed the mock-active fallback. Previously, an OAuthService configured with neither JWKS_URI nor OAUTH_INTROSPECTION_ENDPOINT treated any bearer token as valid — a misconfigured production deployment would silently accept everything. It now returns active: False, matching the TypeScript SDK, which has no such fallback. Has its own named regression test (test_unconfigured_service_rejects_by_default) so this can't silently regress back.

Testing

tests/test_oauth.py grows from 1 test to 19 (pytest tests/test_oauth.py -v — all passing), covering PKCE round-trips and boundary cases, audience validation on both paths, the mock-fallback regression, RFC 8414 required-field coverage, DCR gating (disabled by default, requires both an opt-in flag and a configured client id), and both caches (JWKS client construction count, token-result reuse, TTL expiry).

Full suite (test_basic, test_tasks — 45 passed, test_initial_tool, test_production, test_transports, test_widget_metadata, test_tool_input_schema, test_cli — 59 passed) — zero regressions. examples/flight_booking_server.py still imports and executes cleanly, matching its pre-change baseline. Manually verified the discovery server's actual wire output with curl against /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource.

Add PKCE (RFC 7636) as a standalone, fully-tested utility module — not wired
into a local authorization-code exchange endpoint, since nitrostack is an
OAuth resource server and never issues tokens itself in either SDK.

Expand OAuthService: cache JWKS clients and introspection results (TTL-based),
check the introspection endpoint before JWKS to match the TypeScript SDK's
precedence, and validate the token's audience claim (RFC 8707) on both
validation paths instead of only the JWKS one.

Remove the fallback that treated any bearer token as valid when neither JWKS
nor an introspection endpoint was configured. Unconfigured now means reject,
matching the TypeScript reference, which has no such fallback.

Expand the discovery server: full RFC 8414 authorization-server metadata
instead of a 3-field stub, and a Dynamic Client Registration (RFC 7591)
endpoint using the same simplified static-credential variant the TypeScript
SDK ships — opt-in, hands back pre-configured credentials, no per-client
storage.

Add AuthContext.aud so guard code can see which audience a token was issued
for, normalized to a list since the claim is legal as either a string or a
list.

tests/test_oauth.py grows from 1 test to 19, including a named regression
test for the removed mock-fallback and coverage for the new caches, audience
checks, discovery document shape, and registration gating.
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