Testing Sessions and Cookies
What to test about sessions
Sessions have both functional requirements (they work) and security requirements (they expire, they rotate, they have the right cookie attributes).
Cookie attribute tests
The session cookie must have specific security attributes. Test them:
describe("session cookies", () => {
it("sets HttpOnly flag", async () => {
const res = await login("[email protected]", "password123");
const setCookie = res.headers.get("set-cookie") ?? "";
assert.ok(setCookie.toLowerCase().includes("httponly"), "Cookie must be HttpOnly");
});
it("sets SameSite=Lax", async () => {
const res = await login("[email protected]", "password123");
const setCookie = res.headers.get("set-cookie") ?? "";
assert.ok(setCookie.toLowerCase().includes("samesite=lax"), "Cookie must be SameSite=Lax");
});
it("sets Path=/", async () => {
const res = await login("[email protected]", "password123");
const setCookie = res.headers.get("set-cookie") ?? "";
assert.ok(setCookie.includes("Path=/"), "Cookie must have Path=/");
});
it("sets Max-Age", async () => {
const res = await login("[email protected]", "password123");
const setCookie = res.headers.get("set-cookie") ?? "";
assert.ok(setCookie.includes("Max-Age="), "Cookie must have Max-Age");
});
}); These tests catch regressions where someone accidentally removes a cookie attribute. Without HttpOnly, XSS can steal sessions. Without SameSite, CSRF attacks are possible.
Session lifecycle tests
describe("session lifecycle", () => {
it("valid session accesses protected routes", async () => {
const cookie = await loginAs("[email protected]");
const res = await authenticatedRequest("/me", cookie);
assert.strictEqual(res.status, 200);
});
it("invalid session ID returns 401", async () => {
const res = await authenticatedRequest("/me", "session=fake-id-12345");
assert.strictEqual(res.status, 401);
});
it("empty cookie returns 401", async () => {
const res = await authenticatedRequest("/me", "");
assert.strictEqual(res.status, 401);
});
it("no cookie header returns 401", async () => {
const res = await request("/me");
assert.strictEqual(res.status, 401);
});
it("logout invalidates the session", async () => {
const cookie = await loginAs("[email protected]");
// Logout
await authenticatedRequest("/logout", cookie, { method: "POST" });
// Session should be invalid
const res = await authenticatedRequest("/me", cookie);
assert.strictEqual(res.status, 401);
});
}); Session rotation tests
If your app rotates sessions after privilege changes (from the Production Auth course):
describe("session rotation", () => {
it("password change invalidates old session", async () => {
const cookie = await loginAs("[email protected]");
// Change password (requires step-up in production, simplified here)
const res = await authenticatedRequest("/me/password", cookie, {
method: "PUT",
headers: { "content-type": "application/json" },
body: JSON.stringify({ newPassword: "newpass123" }),
});
// Response should include a new session cookie
const newCookie = getCookie(res);
assert.ok(newCookie, "Should set a new session cookie");
assert.notStrictEqual(newCookie, cookie, "New cookie should differ from old");
// Old session should be invalid
const oldRes = await authenticatedRequest("/me", cookie);
assert.strictEqual(oldRes.status, 401);
// New session should work
const newRes = await authenticatedRequest("/me", newCookie);
assert.strictEqual(newRes.status, 200);
});
}); This test catches a common regression: someone changes the password-update route and forgets to rotate the session. The old session remains valid, which means an attacker who captured it still has access.
Exercises
Exercise 1: Write the cookie attribute tests. Run them. If any fail, fix the cookie helper.
Exercise 2: Write the session lifecycle tests. Verify logout actually invalidates the session.
Exercise 3: Temporarily remove the HttpOnly attribute from your session cookie. Run the tests. The HttpOnly test should fail, catching the regression.
Why do we test cookie attributes like HttpOnly and SameSite?