[{"data":1,"prerenderedAt":425},["ShallowReactive",2],{"blog-best-practices/session":3},{"id":4,"title":5,"body":6,"category":399,"date":400,"dateModified":401,"description":402,"draft":403,"extension":404,"faq":405,"featured":403,"headerVariant":411,"image":412,"keywords":412,"meta":413,"navigation":414,"ogDescription":415,"ogTitle":412,"path":374,"readTime":416,"schemaOrg":417,"schemaType":418,"seo":419,"sitemap":420,"stem":421,"tags":422,"twitterCard":423,"__hash__":424},"blog/blog/best-practices/session.md","Session Management Best Practices: Secure Session Handling",{"type":7,"value":8,"toc":388},"minimark",[9,20,29,34,37,52,130,134,137,146,150,153,162,166,169,245,254,258,261,270,274,277,286,309,328,332,335,357,376],[10,11,12],"tldr",{},[13,14,15,19],"p",{},[16,17,18],"strong",{},"The #1 session security best practice is regenerating session IDs after authentication to prevent session fixation attacks."," Use HttpOnly, Secure, SameSite cookies for session storage. Generate cryptographically random session IDs, regenerate after login, and implement proper session timeout and invalidation. Store sessions server-side with Redis or a database for easy revocation.",[21,22,23],"quotable-box",{},[24,25,26],"blockquote",{},[13,27,28],{},"\"A session is only as secure as its weakest moment. Regenerate on login, timeout on idle, and invalidate on logout.\"",[30,31,33],"h2",{"id":32},"best-practice-1-secure-cookie-settings-5-min","Best Practice 1: Secure Cookie Settings 5 min",[13,35,36],{},"Every session cookie needs these security flags:",[38,39,41],"code-block",{"label":40},"Secure session cookie configuration",[42,43,48],"pre",{"className":44,"code":46,"language":47},[45],"language-text","import session from 'express-session';\nimport RedisStore from 'connect-redis';\nimport { createClient } from 'redis';\n\nconst redisClient = createClient({ url: process.env.REDIS_URL });\nawait redisClient.connect();\n\napp.use(session({\n  store: new RedisStore({ client: redisClient }),\n  name: 'sessionId',  // Custom name (not \"connect.sid\")\n  secret: process.env.SESSION_SECRET,\n  resave: false,\n  saveUninitialized: false,\n  cookie: {\n    httpOnly: true,   // JavaScript cannot access\n    secure: true,     // HTTPS only\n    sameSite: 'lax',  // CSRF protection\n    maxAge: 24 * 60 * 60 * 1000,  // 24 hours\n    domain: '.example.com',  // Specific domain\n    path: '/',\n  },\n}));\n","text",[49,50,46],"code",{"__ignoreMap":51},"",[53,54,55,71],"table",{},[56,57,58],"thead",{},[59,60,61,65,68],"tr",{},[62,63,64],"th",{},"Cookie Flag",[62,66,67],{},"Purpose",[62,69,70],{},"Setting",[72,73,74,86,97,108,119],"tbody",{},[59,75,76,80,83],{},[77,78,79],"td",{},"HttpOnly",[77,81,82],{},"Prevents XSS theft",[77,84,85],{},"Always true",[59,87,88,91,94],{},[77,89,90],{},"Secure",[77,92,93],{},"HTTPS only",[77,95,96],{},"Always true in production",[59,98,99,102,105],{},[77,100,101],{},"SameSite",[77,103,104],{},"CSRF protection",[77,106,107],{},"\"lax\" or \"strict\"",[59,109,110,113,116],{},[77,111,112],{},"Domain",[77,114,115],{},"Cookie scope",[77,117,118],{},"Specific domain, not broad",[59,120,121,124,127],{},[77,122,123],{},"Path",[77,125,126],{},"URL scope",[77,128,129],{},"\"/\" or specific path",[30,131,133],{"id":132},"best-practice-2-regenerate-session-on-auth-changes-3-min","Best Practice 2: Regenerate Session on Auth Changes 3 min",[13,135,136],{},"Prevent session fixation attacks by regenerating the session ID:",[38,138,140],{"label":139},"Session regeneration",[42,141,144],{"className":142,"code":143,"language":47},[45],"// CRITICAL: Regenerate session after login\nasync function login(req, email, password) {\n  const user = await authenticateUser(email, password);\n\n  if (user) {\n    // Regenerate session ID to prevent fixation\n    req.session.regenerate((err) => {\n      if (err) throw err;\n\n      // Store user info in new session\n      req.session.userId = user.id;\n      req.session.role = user.role;\n      req.session.loginTime = Date.now();\n\n      req.session.save((err) => {\n        if (err) throw err;\n        return user;\n      });\n    });\n  }\n}\n\n// Also regenerate after privilege changes\nasync function elevatePrivileges(req, newRole) {\n  req.session.regenerate((err) => {\n    if (err) throw err;\n    req.session.role = newRole;\n    req.session.save();\n  });\n}\n",[49,145,143],{"__ignoreMap":51},[30,147,149],{"id":148},"best-practice-3-session-timeout-strategy-5-min","Best Practice 3: Session Timeout Strategy 5 min",[13,151,152],{},"Implement both idle and absolute timeouts:",[38,154,156],{"label":155},"Session timeout middleware",[42,157,160],{"className":158,"code":159,"language":47},[45],"const IDLE_TIMEOUT = 30 * 60 * 1000;     // 30 minutes\nconst ABSOLUTE_TIMEOUT = 8 * 60 * 60 * 1000; // 8 hours\n\nfunction sessionTimeoutMiddleware(req, res, next) {\n  if (!req.session.userId) {\n    return next();\n  }\n\n  const now = Date.now();\n\n  // Check absolute timeout (since login)\n  if (now - req.session.loginTime > ABSOLUTE_TIMEOUT) {\n    return req.session.destroy(() => {\n      res.status(401).json({ error: 'Session expired' });\n    });\n  }\n\n  // Check idle timeout (since last activity)\n  if (req.session.lastActivity &&\n      now - req.session.lastActivity > IDLE_TIMEOUT) {\n    return req.session.destroy(() => {\n      res.status(401).json({ error: 'Session expired due to inactivity' });\n    });\n  }\n\n  // Update last activity\n  req.session.lastActivity = now;\n  next();\n}\n\napp.use(sessionTimeoutMiddleware);\n",[49,161,159],{"__ignoreMap":51},[30,163,165],{"id":164},"best-practice-4-server-side-session-storage-10-min","Best Practice 4: Server-Side Session Storage 10 min",[13,167,168],{},"Store sessions in Redis or a database for control:",[53,170,171,187],{},[56,172,173],{},[59,174,175,178,181,184],{},[62,176,177],{},"Storage",[62,179,180],{},"Pros",[62,182,183],{},"Cons",[62,185,186],{},"Best For",[72,188,189,203,217,231],{},[59,190,191,194,197,200],{},[77,192,193],{},"Redis",[77,195,196],{},"Fast, TTL support",[77,198,199],{},"Extra infrastructure",[77,201,202],{},"Most applications",[59,204,205,208,211,214],{},[77,206,207],{},"Database",[77,209,210],{},"No extra deps",[77,212,213],{},"Slower, needs cleanup",[77,215,216],{},"Simple apps",[59,218,219,222,225,228],{},[77,220,221],{},"Memory",[77,223,224],{},"Simple",[77,226,227],{},"Lost on restart, no scale",[77,229,230],{},"Development only",[59,232,233,236,239,242],{},[77,234,235],{},"JWT (client)",[77,237,238],{},"Stateless",[77,240,241],{},"Cannot revoke easily",[77,243,244],{},"API-only services",[38,246,248],{"label":247},"Database session store",[42,249,252],{"className":250,"code":251,"language":47},[45],"// Prisma session store example\nimport { PrismaSessionStore } from '@quixo3/prisma-session-store';\nimport { PrismaClient } from '@prisma/client';\n\nconst prisma = new PrismaClient();\n\napp.use(session({\n  store: new PrismaSessionStore(prisma, {\n    checkPeriod: 2 * 60 * 1000,  // Cleanup every 2 min\n    dbRecordIdIsSessionId: true,\n  }),\n  // ... other options\n}));\n\n// Session schema in Prisma\n// model Session {\n//   id        String   @id\n//   sid       String   @unique\n//   data      String\n//   expiresAt DateTime\n// }\n",[49,253,251],{"__ignoreMap":51},[30,255,257],{"id":256},"best-practice-5-proper-session-invalidation-5-min","Best Practice 5: Proper Session Invalidation 5 min",[13,259,260],{},"Clear sessions completely on logout:",[38,262,264],{"label":263},"Complete logout implementation",[42,265,268],{"className":266,"code":267,"language":47},[45],"async function logout(req, res) {\n  const userId = req.session.userId;\n\n  // Destroy the session\n  req.session.destroy((err) => {\n    if (err) {\n      console.error('Session destruction failed:', err);\n    }\n\n    // Clear the cookie\n    res.clearCookie('sessionId', {\n      httpOnly: true,\n      secure: true,\n      sameSite: 'lax',\n      domain: '.example.com',\n      path: '/',\n    });\n\n    res.json({ message: 'Logged out' });\n  });\n}\n\n// Logout from all devices (invalidate all sessions)\nasync function logoutAll(req, res) {\n  const userId = req.session.userId;\n\n  // With Redis: delete all sessions for user\n  const keys = await redisClient.keys(`sess:*`);\n  for (const key of keys) {\n    const session = await redisClient.get(key);\n    if (session && JSON.parse(session).userId === userId) {\n      await redisClient.del(key);\n    }\n  }\n\n  res.json({ message: 'Logged out from all devices' });\n}\n",[49,269,267],{"__ignoreMap":51},[30,271,273],{"id":272},"best-practice-6-session-security-checks-10-min","Best Practice 6: Session Security Checks 10 min",[13,275,276],{},"Add additional validation for sensitive operations:",[38,278,280],{"label":279},"Session binding and validation",[42,281,284],{"className":282,"code":283,"language":47},[45],"// Bind session to browser fingerprint\nfunction createSession(req, user) {\n  req.session.userId = user.id;\n  req.session.fingerprint = hashFingerprint(\n    req.ip,\n    req.headers['user-agent']\n  );\n}\n\n// Validate on each request\nfunction validateSession(req, res, next) {\n  if (!req.session.userId) {\n    return next();\n  }\n\n  const currentFingerprint = hashFingerprint(\n    req.ip,\n    req.headers['user-agent']\n  );\n\n  // Strict mode: reject if fingerprint changes\n  if (req.session.fingerprint !== currentFingerprint) {\n    logger.warn('session.fingerprint_mismatch', {\n      userId: req.session.userId,\n      expected: req.session.fingerprint,\n      actual: currentFingerprint,\n    });\n\n    return req.session.destroy(() => {\n      res.status(401).json({ error: 'Session invalid' });\n    });\n  }\n\n  next();\n}\n\n// Re-authenticate for sensitive actions\nasync function requireRecentAuth(req, res, next) {\n  const AUTH_FRESHNESS = 5 * 60 * 1000; // 5 minutes\n\n  if (Date.now() - req.session.loginTime > AUTH_FRESHNESS) {\n    return res.status(403).json({\n      error: 'Please re-authenticate',\n      code: 'REAUTH_REQUIRED',\n    });\n  }\n\n  next();\n}\n",[49,285,283],{"__ignoreMap":51},[287,288,289],"info-box",{},[13,290,291,294,295,302,303,308],{},[16,292,293],{},"Official Resources:"," For comprehensive session management guidance, see ",[296,297,301],"a",{"href":298,"rel":299},"https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html",[300],"nofollow","OWASP Session Management Cheat Sheet"," and ",[296,304,307],{"href":305,"rel":306},"https://owasp.org/www-community/attacks/Session_fixation",[300],"OWASP Session Fixation Attack",".",[310,311,312,319,325],"faq-section",{},[313,314,316],"faq-item",{"question":315},"Should I use sessions or JWTs?",[13,317,318],{},"Sessions are easier to secure and revoke. Use JWTs for stateless APIs where you need to scale horizontally without shared storage. For web apps with user logins, sessions in HttpOnly cookies are typically safer.",[313,320,322],{"question":321},"What is session fixation?",[13,323,324],{},"An attack where the attacker sets a known session ID before the user logs in. After login, the attacker uses that same session ID to hijack the session. Prevent it by regenerating the session ID after login.",[13,326,327],{},"::faq-item{question=\"How do I handle \"remember me\" functionality?\"}\nUse a separate persistent token stored in a cookie with longer expiry. When the user returns, validate the persistent token and create a new session. Store persistent tokens hashed in the database and rotate them on each use.\n::",[30,329,331],{"id":330},"further-reading","Further Reading",[13,333,334],{},"Put these practices into action with our step-by-step guides.",[336,337,338,345,351],"ul",{},[339,340,341],"li",{},[296,342,344],{"href":343},"/blog/how-to/add-security-headers","Add security headers to your app",[339,346,347],{},[296,348,350],{"href":349},"/blog/checklists/pre-deployment-security-checklist","Pre-deployment security checklist",[339,352,353],{},[296,354,356],{"href":355},"/blog/getting-started/first-scan","Run your first security scan",[358,359,360,366,371],"related-articles",{},[361,362],"related-card",{"description":363,"href":364,"title":365},"Token-based auth","/blog/best-practices/jwt","JWT Best Practices",[361,367],{"description":368,"href":369,"title":370},"Complete auth security","/blog/best-practices/authentication","Authentication",[361,372],{"description":373,"href":374,"title":375},"Secure cookie handling","/blog/best-practices/session","Cookie Security",[377,378,381,385],"cta-box",{"href":379,"label":380},"/","Start Free Scan",[30,382,384],{"id":383},"check-your-session-security","Check Your Session Security",[13,386,387],{},"Scan for session vulnerabilities and misconfigurations.",{"title":51,"searchDepth":389,"depth":389,"links":390},2,[391,392,393,394,395,396,397,398],{"id":32,"depth":389,"text":33},{"id":132,"depth":389,"text":133},{"id":148,"depth":389,"text":149},{"id":164,"depth":389,"text":165},{"id":256,"depth":389,"text":257},{"id":272,"depth":389,"text":273},{"id":330,"depth":389,"text":331},{"id":383,"depth":389,"text":384},"best-practices","2026-02-03","2026-02-16","Session security best practices. Learn secure session creation, cookie settings, session fixation prevention, and proper session invalidation.",false,"md",[406,407,408],{"question":315,"answer":318},{"question":321,"answer":324},{"question":409,"answer":410},"How do I handle 'remember me' functionality?","Use a separate persistent token stored in a cookie with longer expiry. When the user returns, validate the persistent token and create a new session. Store persistent tokens hashed in the database and rotate them on each use.","vibe-green",null,{},true,"Implement secure sessions with proper cookie settings and invalidation.","12 min read","[object Object]","Article",{"title":5,"description":402},{"loc":374},"blog/best-practices/session",[],"summary_large_image","FSh_ub7oCfP_8xKGwCf7VjO_LBISeMGwJE9tSYF24XM",1775843925187]