feat: add OIDC browser sessions with live access enforcement
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / pytest-smoke (push) Failing after 2s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a0e747-8f27-7242-8df8-8bc44f88c929
This commit is contained in:
tegwick 2026-09-28 12:17:16 +02:00
parent fdea2f5192
commit a15fe032b0
14 changed files with 807 additions and 31 deletions

View file

@ -99,3 +99,56 @@ A composed fixture journey verifies a real RSA JWT, fresh facts, an Ed25519
policy decision, receiver custody and native Hub write, followed by entitlement
withdrawal denial. Real root enrollment/login, service deployment, live key and
sender custody, and operational acceptance remain open.
## Browser composition (source candidate)
Pass `browser_settings=BrowserSettings(origin="https://hub.example",
client_id="hub-browser", client_secret_file=Path("/run/secrets/hub-oidc-client"))`
to `create_app`, alongside the enforced controller or authoritative-facts security
composition described above. Import `BrowserSettings` from
`hub_core.security.browser`. Browser configuration cannot activate a development
runtime or a missing controller. Its confidential-client credential is separate
from the policy/audit workload credentials and is reread at each code exchange.
Register the exact HTTPS origin plus `/auth/callback` with the admitted issuer.
The source candidate requires authorization code, S256 PKCE, RS256 ID tokens and
`client_secret_basic` discovery support. It requests fresh authentication and
AAL2; validated access-token assurance and current owner facts decide access.
Issuer registration, actual MFA behavior, audience mapping and live owner
acceptance remain deployment gates. The implementation follows the
[OIDC ID token validation contract](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation)
and [OAuth security best practices](https://www.rfc-editor.org/rfc/rfc9700.html).
- `GET /auth/login` initiates login; arbitrary query parameters are refused.
- `GET /auth/callback` consumes browser-bound state once, exchanges the code with
PKCE, checks signature/issuer/audience/nonce/authentication time/access binding,
and requires root authorization and audit custody before issuing a session.
- `GET /auth/session` rechecks authority and returns expiry and a CSRF token.
- `POST /auth/logout` requires the session, exact `Origin` and `X-Hub-CSRF`, then
destroys the local session even during identity/policy/audit outages.
Sessions contain access tokens only in server memory. Cookies carry random opaque
IDs with `Secure`, `HttpOnly`, `SameSite=Lax`, host-only scope and `/` path. Sessions
expire within five minutes or either token's expiry, whichever comes first;
there is no refresh-token renewal. Process restart/shutdown invalidates sessions.
Multiple workers need sticky routing or a separately reviewed shared session
store; this candidate does not provide distributed sessions. Pending logins and
sessions have bounded capacity. Production ingress must also rate-limit login.
All protected API requests recheck identity, current facts, signed policy and
durable audit. Cookie-authenticated writes additionally require exact `Origin`
and `X-Hub-CSRF`; browser clients obtain the latter from `/auth/session`. The
bundled Swagger UI does not automatically inject that header. Mixed cookies and
bearer credentials are rejected. Session expiry/logout is checked again after
authority awaits and before dispatch. Logout cannot cancel already executing
requests or log out the upstream identity provider; subsequent login requests
fresh authentication. Logout is local invalidation, not a durable audited
business mutation. Refused browser flows are recorded without codes/tokens/query
parameters; audit failure returns 503. Login initiation does not require authority.
The app must observe the configured HTTPS origin. Do not trust arbitrary
forwarded headers; configure an authenticated trusted proxy separately. Callback
query strings are cleared before response-time application access logging, but
reverse proxies and tracing collectors must independently suppress callback
queries, cookies and authorization headers. No public listener is enabled by
these source changes.