{"record":{"id":"ef462b677abd85d3","repo":"remix-run/remix","slug":"session-cookie-must-be-signed","errorCode":null,"errorMessage":"Session cookie must be signed","messagePattern":"Session cookie must be signed","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"critical","filePath":"packages/session-middleware/src/lib/session.ts","lineNumber":17,"sourceCode":"import type { Cookie } from '@remix-run/cookie'\nimport type { Middleware } from '@remix-run/fetch-router'\nimport { Session, type SessionStorage } from '@remix-run/session'\n\n/**\n * Middleware that manages request session state on request context.\n *\n * @param sessionCookie The session cookie to use\n * @param sessionStorage The storage backend for session data\n * @returns The session middleware\n */\nexport function session(\n  sessionCookie: Cookie,\n  sessionStorage: SessionStorage,\n): Middleware<{ key: typeof Session; value: Session; property: 'session' }> {\n  if (!sessionCookie.signed) {\n    throw new Error('Session cookie must be signed')\n  }\n\n  if (sessionCookie.httpOnly === false) {\n    console.warn(\n      `Session cookie \"${sessionCookie.name}\" is configured with httpOnly: false and may be accessible to client-side JavaScript.`,\n    )\n  }\n\n  return async (context, next) => {\n    if (context.has(Session)) {\n      throw new Error('Existing session found, refusing to overwrite')\n    }\n\n    let cookieValue = await sessionCookie.parse(context.headers.get('Cookie'))\n    let session = await sessionStorage.read(cookieValue)\n\n    context.set(Session, session, { property: 'session' })\n","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/remix-run/remix/blob/9696913134be3a4423513d2775f7b31d6917c049/packages/session-middleware/src/lib/session.ts#L1-L35","documentation":"The session() middleware requires the session cookie to be signed, because unsigned cookies can be forged by clients. It throws immediately at middleware construction time if cookie.signed is not enabled.","triggerScenarios":"Calling session(cookie, storage) where the Cookie was created without a secret (or with signed: false), e.g. cookie('session', { path: '/' }) with no secrets option.","commonSituations":"Copying a plain cookie definition into session middleware setup; forgetting the secrets option when migrating from another session library; assuming signing is configured elsewhere.","solutions":["Create the cookie with one or more secrets: cookie('session', { secrets: ['...'] })","Ensure the secrets array is non-empty","Load the secret from an environment variable rather than hardcoding"],"exampleFix":"// before\nlet cookie = createCookie('session', { path: '/' })\nsession(cookie, storage)\n// after\nlet cookie = createCookie('session', { secrets: [process.env.SESSION_SECRET] })\nsession(cookie, storage)","handlingStrategy":"validation","validationCode":"if (!sessionCookie.signed) throw new Error('Configure cookie secrets before using session middleware')","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always pass secrets when creating the session cookie","Fail fast at startup on missing SESSION_SECRET env var"],"tags":["session","cookie","security","middleware"],"backgroundTag":"missing-signing-secret","analyzedSha":"9696913134be3a4423513d2775f7b31d6917c049","analyzedAt":"2026-08-27T19:55:01.024Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}