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.
- Capability
- /docs/authentication/

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
(LoginService → CompleteAsync). 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_csrfcookie +X-Sentinel-Csrfheader) 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 plantedRelayStatesurvives 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
Securecookies — 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
mfaclaim and passkey step-up to demand fresh, phishing-resistant proof; use it where session theft would hurt most.