What it is#
A session identifier connects browser requests to server-side state. Before login, an application may create an anonymous session for preferences, a shopping cart, or CSRF protection. At login, the privilege associated with that session changes.
Session fixation occurs when the identifier does not also change. If an attacker can learn, choose, or cause the victim to use the pre-authentication identifier, the attacker can later present the same value and inherit the authenticated state.
Fixation can also occur during MFA completion, impersonation, elevation to an administrator role, account switching, or linking an identity provider. Any meaningful trust increase should create a fresh session boundary.
Why it matters#
- Hijack a user's authenticated session without learning the password.
- Take over an administrator session after persuading that administrator to authenticate.
- Bypass some login monitoring because the attacker reuses an already established session.
- Retain access across account switching or privilege elevation.
- Combine with session identifiers exposed in URLs, logs, referers, or insecure cookies.
Usually medium to high. Impact depends on how an attacker can set or observe the initial identifier and on the privileges available after authentication. Actual severity depends on context: who is affected, what data or actions are reachable, and existing controls.
Examples#
// Vulnerable: the existing anonymous session becomes authenticated.
app.post('/login', async (req, res) => {
const user = await verifyPassword(req.body.email, req.body.password);
if (!user) return res.sendStatus(401);
req.session.userId = user.id;
req.session.role = user.role;
res.redirect('/app');
});
// Fixed: regenerate first, then place authenticated state in the new session.
app.post('/login', async (req, res, next) => {
const user = await verifyPassword(req.body.email, req.body.password);
if (!user) return res.sendStatus(401);
req.session.regenerate((error) => {
if (error) return next(error);
req.session.userId = user.id;
req.session.role = user.role;
req.session.authenticatedAt = Date.now();
req.session.save((saveError) => {
if (saveError) return next(saveError);
res.redirect('/app');
});
});
});
Only explicitly approved anonymous state, such as a cart identifier, should be copied into the new session.
How to fix it#
1. Regenerate the identifier after authentication. Use the framework's session-regeneration function immediately after primary login and before returning an authenticated response.
2. Rotate after every trust change. Create another fresh identifier after MFA, reauthentication, role elevation, impersonation start or stop, account switching, and recovery completion.
3. Invalidate the previous identifier. Rotation must make the old session unusable, not merely issue a second cookie that points to the same active record.
4. Accept identifiers only from cookies. Do not accept session IDs from URLs, request bodies, or arbitrary headers. Prevent session identifiers from leaking through history, logs, analytics, and referers.
5. Harden cookies. Use Secure, HttpOnly, an appropriate SameSite value, a narrow path, and no broad domain unless required. Prefer host-only cookie prefixes where the platform supports them.
6. Set lifecycle limits. Enforce idle and absolute timeouts server-side. Invalidate sessions on logout, credential reset, account disablement, and suspected theft.
7. Generate strong identifiers. Use the framework's cryptographically secure generator. Do not embed usernames, roles, timestamps, or predictable counters.
8. Protect the session store. Apply tenant isolation, encryption where appropriate, least-privilege access, expiration, and audit controls.
How to test for it#
- Record the session identifier before login with a dedicated test account, then log in and verify the identifier changes.
- Attempt to use the old identifier from a separate browser profile. It must remain anonymous or be invalid.
- Repeat the check after MFA, privilege elevation, account switching, impersonation, password reset, and recovery.
- Confirm a user-supplied cookie value is not adopted as a valid server session.
- Verify identifiers in URLs or request bodies are ignored and are not copied into authentication cookies.
- Test logout and session revocation from a second browser to confirm server-side invalidation.
- Add automated tests asserting identifier rotation and old-session invalidation at every trust transition.