[{"data":1,"prerenderedAt":382},["ShallowReactive",2],{"blog-diagrams/session-fixation-regenerate":3},{"id":4,"title":5,"body":6,"category":349,"date":350,"dateModified":350,"description":351,"draft":352,"extension":353,"faq":354,"featured":352,"headerVariant":365,"image":366,"keywords":368,"meta":369,"navigation":226,"ogDescription":370,"ogTitle":371,"path":372,"readTime":373,"schemaOrg":374,"schemaType":375,"seo":376,"sitemap":377,"stem":378,"tags":379,"twitterCard":380,"__hash__":381},"blog/blog/diagrams/session-fixation-regenerate.md","Session Fixation, Drawn (Diagram)",{"type":7,"value":8,"toc":342},"minimark",[9,13,130,140,146,152,158,161,165,308,326,338],[10,11,12],"p",{},"Nobody guessed a password and nobody stole a cookie. The attacker asked your app for a session ID while signed out, arranged for your browser to carry the same one, and then waited for you to log in and make it valuable.",[14,15,22,27,39,45,54,57,66,72,76,79,93,96,107,111,114,117,120],"diagram",{"alt":16,"caption":17,"height":18,"href":19,"src":20,"width":21},"A sequence diagram of session fixation. The attacker asks your app for a session ID while signed out, mails you a link carrying that ID, and when you sign in your app keeps the same ID instead of issuing a new one, so the attacker's copy is now signed in as you.","The attacker never guesses your session ID. Your app gave it to them first.",1116,"/blog/how-to/session-management","/diagrams/session-fixation-regenerate.png",1604,[23,24,26],"h2",{"id":25},"reading-the-diagram","Reading the diagram",[10,28,29,33,34,38],{},[30,31,32],"strong",{},"Steps 1 and 2."," The attacker visits your site like anyone else and takes the session ID your app hands out to anonymous visitors. Nothing is broken yet. ",[35,36,37],"code",{},"abc123"," is worthless: it belongs to nobody and can see nothing.",[10,40,41,44],{},[30,42,43],{},"The note in the middle."," This is the delivery hop, and it happens outside your app entirely. A link with the session ID in a query parameter, a subdomain the attacker controls setting the cookie, a cross-site scripting bug on a neighbouring page. The mechanics vary. What matters is that your browser and the attacker's browser are now carrying the same session ID.",[10,46,47,50,51,53],{},[30,48,49],{},"Steps 3 and 4, the red band."," You sign in. Your password is correct, your two-factor code is correct, everything about this login is legitimate. Your app looks up ",[35,52,37],{},", marks it as belonging to you, and sends you on your way with the same ID it had a second ago.",[10,55,56],{},"That single decision is the vulnerability. Not a weak password, not a missing check on a route. The app reused a value that a stranger already knew.",[10,58,59,62,63,65],{},[30,60,61],{},"Steps 5 and 6."," The attacker refreshes. Their copy of ",[35,64,37],{}," is the same string it always was, and your app now considers that string signed in as you.",[10,67,68,71],{},[30,69,70],{},"The dead space on the attacker's side."," Look at the gap under \"The attacker\" while you sign in. That emptiness is the point of the diagram: the attacker does nothing during the part where the security actually happens. They do not need to. Your login did the work.",[23,73,75],{"id":74},"the-fix-is-one-line-in-one-place","The fix is one line, in one place",[10,77,78],{},"Issue a new session ID when authentication succeeds, and delete the old one.",[80,81,83],"code-block",{"label":82},"Regenerate at the privilege boundary",[84,85,90],"pre",{"className":86,"code":88,"language":89},[87],"language-text","// after the password and any second factor have been verified\nawait destroySession(oldSessionId);        // the attacker's copy now points at nothing\nconst newSessionId = crypto.randomBytes(32).toString('hex');\nawait createSession(newSessionId, user.id);\nsetSessionCookie(res, newSessionId);\n","text",[35,91,88],{"__ignoreMap":92},"",[10,94,95],{},"The attacker's ID is not blocked, blacklisted, or detected. It simply stops referring to anything, which is stronger than any of those.",[97,98,99,104],"warning-box",{},[100,101,103],"h4",{"id":102},"login-is-not-the-only-boundary","Login is not the only boundary",[10,105,106],{},"Regenerate on password change and on any role or permission change too. If someone was signed in as a normal user and you promote them to admin without a new ID, any pre-existing copy of that ID is now an admin session.",[23,108,110],{"id":109},"why-ai-generated-auth-misses-this","Why AI-generated auth misses this",[10,112,113],{},"Session fixation is invisible in a working app. Log in, everything works. Log out, everything works. There is no error, no failing test, and no user-facing symptom until someone deliberately arranges the setup above.",[10,115,116],{},"Generated auth code optimises for the flow that gets exercised: create a session, read it back, delete it on logout. Regeneration sits in none of those paths, so it drops out. We see this pattern in scans of AI-built apps regularly, alongside its sibling: the logout that clears the cookie in the browser without deleting the row on the server.",[10,118,119],{},"Thirty-second check, no tooling: open devtools, look at your session cookie signed out, sign in, look again. Same value means you are vulnerable.",[121,122,123],"faq-section",{},[124,125,127],"faq-item",{"question":126},"What is session fixation?",[10,128,129],{},"An attack where the attacker chooses your session ID before you log in, rather than stealing it afterwards. They take an anonymous session ID from the app, plant it in your browser, and wait. If the app carries that ID through login, their copy becomes an authenticated session for your account.",[124,131,133],{"question":132},"How is session fixation different from session hijacking?",[10,134,135,136,139],{},"Hijacking is theft after the fact, so the attacker has to get hold of a live session cookie somehow. Fixation is arranged in advance and needs no theft, because the attacker picked the ID. That difference is why an ",[35,137,138],{},"httpOnly"," cookie defeats one and not the other.",[124,141,143],{"question":142},"How do I fix session fixation?",[10,144,145],{},"Issue a brand new session ID the moment authentication succeeds and delete the old one. That single change closes the whole attack, because the ID the attacker holds stops pointing at anything. Do the same on password change and on any role change.",[124,147,149],{"question":148},"Does httpOnly or SameSite stop session fixation?",[10,150,151],{},"No. Both exist to stop an attacker reading or sending your cookie, and fixation needs neither. Keep them, they are doing other useful work, but regeneration at the privilege boundary is the control for this one.",[124,153,155],{"question":154},"How do I test whether my app is vulnerable?",[10,156,157],{},"Open your app signed out, copy the session cookie value from devtools, sign in, and look again. Identical value means no regeneration happened and fixation works. It is about a thirty-second check and it needs no tools beyond the browser.",[10,159,160],{},"::",[23,162,164],{"id":163},"the-source","The source",[84,166,170],{"className":167,"code":168,"language":169,"meta":92,"style":92},"language-mermaid shiki shiki-themes github-dark","---\ntitle: \"The planted session ID survives login\"\n---\nsequenceDiagram\n    autonumber\n    participant A as The attacker\n    participant S as Your app\n    participant B as Your browser\n\n    A->>S: opens your site, signed out\n    S-->>A: here is a session ID: abc123\n\n    Note over A,B: then mails you a link with abc123 in it\n\n    rect rgb(254, 242, 242)\n        B->>S: you sign in, still carrying abc123\n        S-->>B: welcome back. same session ID.\n    end\n\n    A->>S: reloads, still holding abc123\n    S-->>A: your account, your data\n\n    Note over A,B: nothing was stolen. your app reused the ID\u003Cbr/>instead of issuing a new one at sign-in.\n","mermaid",[35,171,172,180,186,191,197,203,209,215,221,228,234,240,245,251,256,262,268,274,280,285,291,297,302],{"__ignoreMap":92},[173,174,177],"span",{"class":175,"line":176},"line",1,[173,178,179],{},"---\n",[173,181,183],{"class":175,"line":182},2,[173,184,185],{},"title: \"The planted session ID survives login\"\n",[173,187,189],{"class":175,"line":188},3,[173,190,179],{},[173,192,194],{"class":175,"line":193},4,[173,195,196],{},"sequenceDiagram\n",[173,198,200],{"class":175,"line":199},5,[173,201,202],{},"    autonumber\n",[173,204,206],{"class":175,"line":205},6,[173,207,208],{},"    participant A as The attacker\n",[173,210,212],{"class":175,"line":211},7,[173,213,214],{},"    participant S as Your app\n",[173,216,218],{"class":175,"line":217},8,[173,219,220],{},"    participant B as Your browser\n",[173,222,224],{"class":175,"line":223},9,[173,225,227],{"emptyLinePlaceholder":226},true,"\n",[173,229,231],{"class":175,"line":230},10,[173,232,233],{},"    A->>S: opens your site, signed out\n",[173,235,237],{"class":175,"line":236},11,[173,238,239],{},"    S-->>A: here is a session ID: abc123\n",[173,241,243],{"class":175,"line":242},12,[173,244,227],{"emptyLinePlaceholder":226},[173,246,248],{"class":175,"line":247},13,[173,249,250],{},"    Note over A,B: then mails you a link with abc123 in it\n",[173,252,254],{"class":175,"line":253},14,[173,255,227],{"emptyLinePlaceholder":226},[173,257,259],{"class":175,"line":258},15,[173,260,261],{},"    rect rgb(254, 242, 242)\n",[173,263,265],{"class":175,"line":264},16,[173,266,267],{},"        B->>S: you sign in, still carrying abc123\n",[173,269,271],{"class":175,"line":270},17,[173,272,273],{},"        S-->>B: welcome back. same session ID.\n",[173,275,277],{"class":175,"line":276},18,[173,278,279],{},"    end\n",[173,281,283],{"class":175,"line":282},19,[173,284,227],{"emptyLinePlaceholder":226},[173,286,288],{"class":175,"line":287},20,[173,289,290],{},"    A->>S: reloads, still holding abc123\n",[173,292,294],{"class":175,"line":293},21,[173,295,296],{},"    S-->>A: your account, your data\n",[173,298,300],{"class":175,"line":299},22,[173,301,227],{"emptyLinePlaceholder":226},[173,303,305],{"class":175,"line":304},23,[173,306,307],{},"    Note over A,B: nothing was stolen. your app reused the ID\u003Cbr/>instead of issuing a new one at sign-in.\n",[309,310,311,316,321],"related-articles",{},[312,313],"related-card",{"description":314,"href":19,"title":315},"The full implementation this diagram belongs to, including storage, expiry, and revocation.","Secure Session Management",[312,317],{"description":318,"href":319,"title":320},"The other half of the session story: what a stateless token cannot do once it is issued.","/blog/diagrams/session-lifecycle-and-the-jwt-gap","Where a JWT Cannot Be Revoked",[312,322],{"description":323,"href":324,"title":325},"The wider category fixation sits in, and the other ways login logic fails quietly.","/blog/vulnerabilities/broken-auth","Broken Authentication",[327,328,331,335],"cta-box",{"href":329,"label":330},"/","Start Free Scan",[23,332,334],{"id":333},"does-your-login-issue-a-new-session","Does Your Login Issue a New Session?",[10,336,337],{},"CheckYourVibe reads what your deployed app actually sets, including a session cookie that never changes across the login boundary.",[339,340,341],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":92,"searchDepth":182,"depth":182,"links":343},[344,345,346,347,348],{"id":25,"depth":182,"text":26},{"id":74,"depth":182,"text":75},{"id":109,"depth":182,"text":110},{"id":163,"depth":182,"text":164},{"id":333,"depth":182,"text":334},"diagrams","2026-08-27","A sequence diagram of session fixation. The attacker gets a session ID from your own app, leaves it in your browser, and your login hands them your account without changing it.",false,"md",[355,357,359,361,363],{"question":126,"answer":356},"An attack where the attacker chooses your session ID before you log in, rather than stealing it after. They get an anonymous session ID from the app, plant it in your browser, and wait. If the app keeps that ID through login, their copy is now an authenticated session for your account.",{"question":132,"answer":358},"Hijacking is theft after the fact: the attacker has to get hold of a live session cookie somehow. Fixation is arranged in advance and needs no theft at all, because the attacker already knows the ID. That is why an httpOnly cookie stops one and not the other.",{"question":142,"answer":360},"Issue a brand new session ID the moment authentication succeeds, and delete the old one. One new ID at login closes the entire attack, because the ID the attacker holds now points at nothing. Do the same on password change and on any role or permission change.",{"question":148,"answer":362},"No. Both are about stopping an attacker reading or sending your cookie, and fixation needs neither. The attacker is not reading your session ID, they picked it. Regeneration at the privilege boundary is the control that matters here.",{"question":154,"answer":364},"Open your app signed out, note the session cookie value in devtools, then sign in and look again. If the value is identical, your app did not regenerate and fixation works against it. That check takes about thirty seconds.","blue",{"src":367,"alt":16},"https://checkyourvibe.dev/diagrams/session-fixation-regenerate.png","session fixation diagram, session fixation attack, regenerate session id on login, session id not changing after login, session hijacking, secure session management",{"ogImage":367},"Nothing was stolen and no password was guessed. The attacker's session ID was already in your browser, and signing in did not replace it.",null,"/blog/diagrams/session-fixation-regenerate","4 min read","[object Object]","Article",{"title":5,"description":351},{"loc":372},"blog/diagrams/session-fixation-regenerate",[],"summary_large_image","QMlvNF82LTAtGju5QFRRgl9JU8VGVlSpr38IdsJhdLU",1789672852804]