Security recipes · 01

Session fixation

The threat
Plant a known session identifier before the victim logs in, then reuse it after authentication elevates it.
Sentinel's counter
Sessions and tokens are minted server-side only after credential verification; nothing presented pre-authentication survives into the authenticated state.

The attack

Classic session fixation targets systems that create a session before authentication and keep it after: the attacker obtains a valid pre-auth session id (by visiting the login page themselves, or by injecting one via URL parameter or cookie-tossing), plants it in the victim’s browser, and waits. The victim logs in; the server upgrades the planted session to authenticated; the attacker — still holding the same id — is now logged in as the victim.

The attack needs three ingredients: sessions that exist pre-authentication, session ids that are attacker-suppliable, and a login that elevates rather than replaces.

How Sentinel counters it

Sentinel’s session model denies all three ingredients structurally.

There is no pre-authentication session. A Session row is created inside the login flow, after the abuse gate, credential verification and the risk gate have all passed (LoginServiceCompleteAsync). Before that moment there is nothing server-side for an attacker to fixate. The only pre-auth artifacts — the mfa_pending token, passkey ceremony ids — are single-purpose, short-lived, and cannot become sessions themselves: the MFA pending token is a signed JWT with typ: "mfa+sentinel" that only POST /auth/mfa/verify accepts, and completing it mints a fresh session.

Session identity is not client-suppliable. The session id (sid) exists in exactly two server-minted forms: inside the signed access token’s claims, and hashed inside the refresh-token record. There is no session cookie whose value a script or URL parameter could set; the auth cookies (sentinel_at, sentinel_rt) carry tokens, httpOnly, and their values are only ever produced by Sentinel’s own minting path. A planted value fails RS256 signature verification — with typ and audience enforced, so no token of another type can masquerade as an access token.

Login replaces, never elevates. Every successful login — password, MFA-completed, passkey, federated — creates a new session and new token family. Passkey logins additionally mark the fresh session’s MfaLevel (PhishingResistant), which is minted into the token; assurance level is a property of the new session, never something an old context inherits.

Two adjacent hardening details close the flanks:

  • CSRF double-submit (sentinel_csrf cookie + X-Sentinel-Csrf header) on unsafe cookie-mode requests: a cookie-tossing attacker who can set cookies on a related domain still can’t read the CSRF value from your origin, so they can’t drive the session they’d hoped to plant.
  • SAML/federation replay caches: an assertion consumed once is consumed forever (Replayed_assertion_id_is_rejected_on_second_use), and federation state is single-use — no planted RelayState survives to fixate a federated login either (Replayed_relay_state_is_rejected_on_the_sp_initiated_path).

What your app must still do

  • Don’t build your own session layer on top. If you wrap Sentinel with a framework session (e.g. ASP.NET session state) keyed pre-login, you can reintroduce fixation one layer up. Let the Sentinel principal be the authentication state.
  • Serve everything over HTTPS with Secure cookies — Sentinel sets cookie flags, but transport downgrade protection (HSTS) is host configuration.
  • Scope cookies deliberately if you host multiple apps under one apex domain; cookie-tossing from a compromised sibling subdomain is a host-topology risk no identity library can remove.
  • Re-authenticate for sensitive transitions (payout changes, credential enrollment). Sentinel gives you the mfa claim and passkey step-up to demand fresh, phishing-resistant proof; use it where session theft would hurt most.