Skip to content

Security: full RFC 9700 / OAuth 2.1 + Spring Security parity (audit remediation)#150

Open
ancongui wants to merge 7 commits into
mainfrom
fix/security-high-severity-rfc9700
Open

Security: full RFC 9700 / OAuth 2.1 + Spring Security parity (audit remediation)#150
ancongui wants to merge 7 commits into
mainfrom
fix/security-high-severity-rfc9700

Conversation

@ancongui

@ancongui ancongui commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Implements the complete remediation of the RFC 9700 / OAuth 2.1 / Spring Security parity audit — both the High-severity findings and every deferred item — test-driven throughout. ~5021 tests pass, mypy --strict clean (691 files), ruff clean.

Wave 1 — OAuth security MUSTs (shipped defaults)

  • PKCE on by default for the authorization_code login flow; always enforced for public clients; per-registration config.
  • client_credentials scope validation — unregistered scopes rejected (INVALID_SCOPE), closing a privilege-escalation primitive.
  • Signing-secret hardening — refuses the placeholder secret + enforces ≥32-byte HMAC keys (only where used); optional aud claim.
  • IdP ROPC gated off — Keycloak/Entra/Cognito grant_type=password requires explicit allow-password-grant.

Wave 2 — Spring core parity

  • DelegatingPasswordEncoder ({id} + upgrade) + Argon2 / PBKDF2 / SCrypt encoders.
  • CSRF secure-by-default (cookie-gated; stateless clients unaffected).
  • HTTP-method request matchers in the URL DSL.
  • HTTP Basic auth + UserDetailsService SPI.

Wave 3 — OAuth server completeness + advanced

  • RFC 9207 iss mix-up validation in the callback.
  • Refresh-token reuse/family detection — replaying a rotated token revokes the family.
  • Asymmetric AS signing (RS256/ES256/PS*) + /oauth2/jwks publication.
  • Introspection (RFC 7662) + Revocation (RFC 7009) endpoints (AuthorizationServerEndpoints) + OpaqueTokenIntrospector — owner-scoped per RFC 7009 §2.1; empty client-auth rejected.
  • DPoP (RFC 9449) + mTLS (RFC 8705) sender-constrained tokens — AS cnf binding + opt-in resource-server proof-of-possession enforcement.

Wave 4 — authn/authz breadth

  • AuthenticationManager/ProviderManager + DaoAuthenticationProvider (timing-equalised, credential erasure).
  • PermissionEvaluator SPI wired into hasPermission(target[, type], perm).
  • @pre_filter / @post_filter collection filtering (filterObject).
  • Form login + generic logout filters with config-driven auto-config.

Intentional behavior changes (hardening)

Fail-fast on placeholder signing secrets · CSRF on by default (cookie-gated; opt out with pyfly.security.csrf.enabled=false) · PKCE sent by default · ROPC requires opt-in · client_credentials rejects unregistered scopes.

Test plan

  • pytest — ~5021 passed, 7 skipped, integration deselected (~230 new security tests)
  • mypy --strict src/pyfly — clean (691 files)
  • ruff check + ruff format --check — clean
  • A background commit security-review on the introspection/revocation endpoints was addressed (owner-scoped revoke/introspect, empty-credential rejection).

Remaining (niche / out of scope)

X.509, run-as / switch-user, dynamic client registration (RFC 7591), PAR (9126) / JAR (9101), and JDBC-backed ACL/user stores.

ancongui added 7 commits June 25, 2026 23:51
… ROPC, CSRF, encoders, HTTP Basic)

Implements the High-severity findings from the security parity audit. TDD
throughout; full suite (4950 passed) + mypy --strict + ruff all green.

OAuth / token security
- PKCE on by default for the authorization_code login flow; always enforced for
  public clients (empty client_secret); config-bindable per registration.
- AuthorizationServer rejects client_credentials scopes the client is not
  registered for (INVALID_SCOPE, RFC 6749 §5.2) — closes a privilege-escalation
  primitive.
- Composition root refuses the built-in placeholder signing secret and enforces
  a >=32-byte HMAC key (RFC 7518 §3.2), only when the signer is actually used;
  optional audience -> aud claim on issued tokens.
- IdP cloud adapters (Keycloak/Entra/Cognito) disable the ROPC
  grant_type=password flow unless pyfly.idp.allow-password-grant=true
  (OAuth 2.1 removal / RFC 9700 §2.4).

Spring Security parity
- DelegatingPasswordEncoder ({id}-prefix + upgrade_encoding) plus Pbkdf2 / Scrypt
  / Argon2 encoders behind the PasswordEncoder port; opt-in delegating bean.
- CSRF secure-by-default (active unless csrf.enabled=false) in cookie-gated mode,
  so stateless/token (no-cookie) clients are unaffected.
- HTTP-method-scoped request matchers in the HttpSecurity URL DSL.
- HTTP Basic authentication: UserDetailsService SPI + HttpBasicAuthenticationFilter
  + config-driven auto-configuration.

Deferred (documented): form login, generic LogoutConfigurer, RFC 9207 iss check,
refresh-token reuse detection, RS256/JWKS AS signing, introspection/revocation
endpoints, DPoP/mTLS, AuthenticationManager/PermissionEvaluator/@PreFilter.
…n, asymmetric AS signing + JWKS

- OAuth2 client callback validates the RFC 9207 iss param (mismatch aborts; require_iss opt-in)
- AuthorizationServer detects refresh-token reuse and revokes the whole rotation family (OAuth 2.1)
- AuthorizationServer supports RS256/ES256/PS* signing with a private key + kid; jwks() publishes the public JWK set so a resource server can verify AS-minted tokens
…endpoints + OpaqueTokenIntrospector

- AuthorizationServer.introspect() (access JWT + refresh token) and authenticate_client()
- AuthorizationServerEndpoints: POST /oauth2/token, /oauth2/introspect, /oauth2/revoke, GET /oauth2/jwks (client-authed via Basic or post)
- OpaqueTokenIntrospector: resource-server validation of opaque tokens via an introspection endpoint, shared claim mapping
…eject empty client auth

Addresses commit security-review findings on the OAuth2 management endpoints:
- Revocation is owner-only (RFC 7009 §2.1) — a client can no longer revoke another client's tokens.
- Introspection no longer discloses another client's token; only the owner, or a client marked allow_introspection (resource server), may introspect it.
- authenticate_client refuses empty client_id/secret and clients with no configured secret (no empty-credential bypass on /introspect and /revoke).
Also adds the DPoP/mTLS validation library (RFC 9449 / RFC 8705): jwk_thumbprint, DPoPProofValidator, certificate_thumbprint, confirm_dpop_binding/confirm_mtls_binding.
…tokens — AS binding + RS enforcement

- AuthorizationServer.token() accepts a cnf confirmation; token endpoint binds a DPoP proof (cnf.jkt)
- OAuth2ResourceServerFilter: opt-in enforce_sender_constraints accepts Bearer + DPoP schemes and, for cnf-bound tokens, verifies the DPoP proof (htm/htu/iat/jti/ath + jkt) or the mTLS client-cert thumbprint
- JWKSTokenValidator.validate_and_context(); resource-server config: enforce-sender-constraints, mtls-cert-header
…filter/@post_filter

- authentication.py: Authentication, AuthenticationProvider, ProviderManager, DaoAuthenticationProvider (UserDetailsService + PasswordEncoder, timing-equalised, credential erasure)
- permission.py: PermissionEvaluator port; hasPermission(target[, type], perm) dispatches to it (flat-permission fallback)
- method_security: @pre_filter / @post_filter collection filtering with filterObject binding
…n auto-config

- FormLoginFilter: username/password POST authenticated via ProviderManager, session-id rotation (fixation defense), SecurityContext stored in session; redirect or JSON responses
- LogoutFilter: invalidates the session, clears the security context, deletes configured cookies; redirect or 204
- Config-driven FormLoginAutoConfiguration + LogoutAutoConfiguration (pre-hashed users), registered as entry points; shared _users_from_config helper
@ancongui ancongui changed the title Security hardening: RFC 9700 / OAuth 2.1 + Spring parity (PKCE, ROPC, CSRF, password encoders, HTTP Basic) Security: full RFC 9700 / OAuth 2.1 + Spring Security parity (audit remediation) Jun 25, 2026
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