Skip to content

Bound session lifetime and periodically re-validate identity and tenant - #64

Merged
einari merged 5 commits into
mainfrom
fix/cookie-session-hardening
Jul 30, 2026
Merged

Bound session lifetime and periodically re-validate identity and tenant#64
einari merged 5 commits into
mainfrom
fix/cookie-session-hardening

Conversation

@einari

@einari einari commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Summary

Hardens the cookie/session posture so a browser holding old cookies cannot retain access indefinitely: revoking a user's access now takes effect within minutes without validating against backing services on every request.

Changed

  • The auth session is bounded by an absolute lifetime (default 12h, configurable via Cratis:AuthProxy:Session:Lifetime); sliding expiration is off by default so activity cannot extend a session indefinitely. The auth cookie remains session-scoped, so closing the browser ends it.
  • The identity-details cookie now expires after the identity re-validation interval (default 10 min), forcing periodic re-resolution of identity and authorization instead of living for the whole browser session.
  • A tenant resolved through the selection strategy is re-validated against the tenants endpoint on an interval (default 10 min); when the tenant is no longer available to the user, the tenant cookies are cleared and the request replays through fresh resolution.
  • Auth, correlation, and nonce cookies now use SecurePolicy.SameAsRequest instead of None.

Added

  • Cratis:AuthProxy:Session configuration section: Lifetime, SlidingExpiration, IdentityRevalidationInterval, TenantRevalidationInterval (zero/negative interval opts out), with documentation.

All 576 specs pass (28 new covering the option defaults, cookie configuration, identity cookie expiry, and tenant re-validation). Not verified live against a deployed IdP — in-process spec coverage only.

🤖 Generated with Claude Code

einari and others added 5 commits July 30, 2026 08:39
…vals

Cookies that establish identity and tenant context were either unbounded
within a browser session or trusted indefinitely once issued, so revoked
access could keep working for as long as the browser stayed open. This
introduces a Cratis:AuthProxy:Session section that bounds all three:

- Lifetime (default 12h, absolute) caps the authentication ticket.
- IdentityRevalidationInterval (default 10m) caps how long resolved
  identity details are trusted before re-resolution.
- TenantRevalidationInterval (default 10m) caps how long a selected
  tenant is trusted before re-validation.

Defaults are chosen so bounded re-validation happens without per-request
backend calls; zero or negative intervals opt out.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The authentication ticket previously used the ASP.NET Core default of a
14-day sliding expiration, so an active browser session effectively
never re-authenticated and revoked users kept access indefinitely. The
ticket lifetime now comes from Session:Lifetime (default 12 hours) with
sliding expiration disabled by default, forcing a periodic round-trip to
the identity provider while the cookie itself stays session-scoped.

The auth, correlation, and nonce cookies also move from SecurePolicy
None to SameAsRequest: they are marked Secure whenever the original
request is HTTPS (forwarded headers preserve the scheme behind a
TLS-terminating ingress) while local HTTP development keeps working.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The .cratis-identity cookie short-circuits identity resolution: while it
exists, the services' identity endpoints (and the authorization decision
they return) are never consulted again. As a pure session cookie that
meant a browser session that never closed also never re-validated, so a
user whose access was revoked stayed authorized until they closed the
browser.

The cookie now carries Max-Age equal to
Session:IdentityRevalidationInterval (default 10 minutes), making the
browser drop it periodically so identity is re-resolved - including the
403/unauthorized path - without a backend round-trip on every request.
A non-positive interval restores the pure session cookie.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A tenant resolved from the .cratis-tenant selection cookie was trusted
for the entire browser session - once selected, the tenants endpoint was
never consulted again, so a user removed from a tenant kept acting in it
until the browser closed.

The tenant selection middleware now re-validates a selection-resolved
tenant against the configured TenantsEndpoint once per
Session:TenantRevalidationInterval (default 10 minutes), caching the
positive answer in memory so no per-request backend calls are made.
When the endpoint answers authoritatively that the tenant is no longer
available, the .cratis-tenant and .cratis-tenants cookies are deleted
and the request is replayed without them so the regular selection or
no-tenant flow takes over. Transport failures fail open so a transient
backend outage cannot lock users out.

Explicit selections and single-tenant auto-selection stamp the
re-validation window, and non-selection strategies are unaffected since
they derive the tenant from the request itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Describe the new Cratis:AuthProxy:Session section with its defaults, the
session-scoped and Secure/HttpOnly/SameSite posture of the cookies, and
how the selected tenant is periodically re-validated so revoked access
takes effect without per-request backend calls.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@einari einari added the patch label Jul 30, 2026
@einari
einari merged commit 27b2489 into main Jul 30, 2026
3 checks passed
@einari
einari deleted the fix/cookie-session-hardening branch July 30, 2026 07:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant