Skip to content

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

Merged
ancongui merged 12 commits into
mainfrom
fix/security-high-severity-rfc9700
Jun 26, 2026
Merged

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

Conversation

@ancongui

@ancongui ancongui commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Complete remediation of the RFC 9700 / OAuth 2.1 / Spring Security parity audit — every finding, every deferred item, and a full authorization-code authorization server. Test-driven throughout. ~5072 tests pass, mypy --strict clean (694 files), ruff clean.

Wave 1 — OAuth security MUSTs (shipped defaults)

PKCE on by default (forced for public clients) · client_credentials scope validation · signing-secret fail-fast + ≥32-byte HMAC + optional aud · IdP ROPC gated behind opt-in.

Wave 2 — Spring core parity

DelegatingPasswordEncoder + Argon2/PBKDF2/SCrypt · CSRF secure-by-default (cookie-gated) · HTTP-method request matchers · HTTP Basic + UserDetailsService SPI.

Wave 3 — OAuth server + advanced

RFC 9207 iss validation · refresh-token reuse/family detection · asymmetric AS signing + /oauth2/jwks · introspection (7662) + revocation (7009) (owner-scoped) + OpaqueTokenIntrospector · DPoP (9449) + mTLS (8705) sender-constrained tokens.

Wave 4 — authn/authz breadth

AuthenticationManager/ProviderManager + DaoAuthenticationProvider · PermissionEvaluator · @pre_filter/@post_filter · form login + generic logout (config-driven auto-config).

Wave 5 — persistence + tail

SqlUserDetailsService (SQLAlchemy) · Dynamic Client Registration (RFC 7591) · X.509 client-cert auth · switch-user / run-as.

Wave 6 — full authorization-code authorization server

  • authorization_code grant: AuthorizationServer.authorize() issues single-use codes with exact redirect matching, scope subset, and mandatory PKCE (S256); token exchange verifies PKCE + redirect, mints access/refresh, and revokes on code reuse (injection defense). Public clients authenticate via PKCE.
  • OIDC id_token (sub/aud/iss/nonce) for the openid scope.
  • GET /oauth2/authorize endpoint with resource-owner session gate (login redirect), correct non-redirectable vs redirectable error handling.
  • RFC 8414 AS metadata + OIDC discovery (/.well-known/oauth-authorization-server, /.well-known/openid-configuration).
  • PAR (RFC 9126)POST /oauth2/par → one-time request_uri. JAR (RFC 9101) — signed request objects.

Intentional behavior changes (hardening)

Fail-fast on placeholder signing secrets · CSRF on by default (cookie-gated; opt out via config) · PKCE sent by default · ROPC requires opt-in · client_credentials rejects unregistered scopes.

Test plan

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

The full RFC 9700 / OAuth 2.1 / Spring Security parity audit is now implemented. Possible future polish only: asymmetric (public-key) JAR, a consent UI, and JDBC-backed client/ACL repositories.

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
ancongui added 3 commits June 26, 2026 10:33
…+ switch-user

- SqlUserDetailsService: SQLAlchemy-backed persistent user store (injected engine, lazy table, upsert/delete) for HTTP Basic / form login
- Dynamic Client Registration (RFC 7591): AuthorizationServer.register_client + POST /oauth2/register (open or initial-access-token protected)
- X509AuthenticationFilter: principal from forwarded client cert (CN / subject regex), optional UserDetailsService authority lookup
- SwitchUserFilter: run-as impersonation with original-principal restore + PREVIOUS_ADMINISTRATOR marker
…thorize, metadata, PAR, JAR

- authorization_code grant: AuthorizationServer.authorize() issues single-use codes (exact redirect match, scope subset, mandatory PKCE S256); token() exchange verifies PKCE + redirect, mints access/refresh, reuse->revoke (code-injection defense); public clients allowed via PKCE
- OIDC id_token (sub/aud/iss/nonce) for the openid scope
- GET /oauth2/authorize endpoint: resource-owner session gate (login redirect), redirect with code+state+iss; non-redirectable vs redirectable error handling
- RFC 8414 AS metadata + OIDC discovery (/.well-known/*)
- PAR (RFC 9126): POST /oauth2/par -> one-time request_uri; JAR (RFC 9101): signed request objects (client_secret HS256)
…hapter, parity tables

- New docs/modules/oauth2.md (1019 lines): the OAuth 2.1 / OpenID Connect guide —
  resource server, client & login, full authorization server (client_credentials,
  refresh+reuse, authorization_code+PKCE, OIDC id_token, asymmetric signing+JWKS,
  introspection/revocation, DCR, PAR, JAR, metadata/discovery), DPoP/mTLS, and a
  config + RFC 9700/OAuth 2.1 compliance reference. Wired into the MkDocs nav.
- docs/modules/security.md: new Authentication Mechanisms (UserDetailsService,
  AuthenticationManager/DaoAuthenticationProvider, form login, HTTP Basic, X.509,
  logout, switch-user), Security Headers, and Secure-by-Default & Hardening
  sections; delegating/Argon2/PBKDF2/scrypt encoders; HTTP-method matchers;
  @pre_filter/@post_filter + PermissionEvaluator; CSRF default-on; OAuth2 section
  trimmed to an overview linking oauth2.md. Architecture table, exports, TOC updated.
- book/manuscript/14-security.md: narrative sections — form login, stronger password
  hashing, Lumen as its own authorization server, sender-constrained tokens, secure-by-default.
- docs/spring-comparison.md: security parity rows for the new auth/OAuth2 features.
- docs/modules/idp.md: ROPC (allow-password-grant) opt-in note.
- docs/modules/web-filters.md: corrected/expanded the security filter ordering diagram.
- README.md: refreshed the Security module description.

All config keys/classes verified against source; `mkdocs build` succeeds.
@ancongui

Copy link
Copy Markdown
Contributor Author

📘 Documentation added (commit `21441bc`)

The security capabilities in this PR are now fully documented across the MkDocs site, the book, and the comparison/README:

  • New page docs/modules/oauth2.md — the OAuth 2.1 & OpenID Connect guide (resource server, client & login, full authorization server with PKCE / OIDC id_token / JWKS / introspection / revocation / DCR / PAR / JAR / metadata, DPoP & mTLS, RFC 9700 compliance + config reference). Wired into the nav under Security & Identity.
  • docs/modules/security.md — new Authentication Mechanisms (UserDetailsService, AuthenticationManager / DaoAuthenticationProvider, form login, HTTP Basic, X.509, logout, switch-user), Security Headers, and Secure-by-Default & Hardening sections; delegating/Argon2/PBKDF2/scrypt encoders; HTTP-method matchers; @pre_filter/@post_filter + PermissionEvaluator; CSRF default-on.
  • Book14-security.md gains narrative sections (form login, stronger password hashing, Lumen as its own authorization server, sender-constrained tokens, secure-by-default).
  • spring-comparison.md, idp.md (ROPC opt-in), web-filters.md (filter ordering), and the README Security row updated.

Every config key and class in the docs was verified against the source; mkdocs build succeeds.

ancongui added 2 commits June 26, 2026 13:30
… + Spring parity)

- pyproject.toml / src/pyfly/__init__.py / README badge -> 26.06.114
- CHANGELOG.md: v26.06.114 (2026-06-26) entry (Added / Changed / Security / Documentation)
- README: version badge + changelog highlight
- manuscript-es/14-security.md: translated form login, password hashing,
  authorization server, sender-constrained tokens, secure-by-default sections
  (parity with the English chapter; verify_code + book tests green)
- Rebuilt pyfly-by-example{,-es}.{epub,pdf} so the release ships the new content
@ancongui ancongui merged commit 06861e0 into main Jun 26, 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.

1 participant