Security: full RFC 9700 / OAuth 2.1 + Spring Security parity (audit remediation)#150
Merged
Conversation
… 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
…+ 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.
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:
Every config key and class in the docs was verified against the source; |
… + 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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 --strictclean (694 files),ruffclean.Wave 1 — OAuth security MUSTs (shipped defaults)
PKCE on by default (forced for public clients) ·
client_credentialsscope validation · signing-secret fail-fast + ≥32-byte HMAC + optionalaud· 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 +UserDetailsServiceSPI.Wave 3 — OAuth server + advanced
RFC 9207
issvalidation · 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_codegrant: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.id_token(sub/aud/iss/nonce) for theopenidscope.GET /oauth2/authorizeendpoint with resource-owner session gate (login redirect), correct non-redirectable vs redirectable error handling./.well-known/oauth-authorization-server,/.well-known/openid-configuration).POST /oauth2/par→ one-timerequest_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_credentialsrejects 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— cleanThe 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.