{"record":{"id":"182eefaa087882c2","repo":"nextauthjs/next-auth","slug":"must-pass-secret-if-not-set-to-jwt-gettoken","errorCode":null,"errorMessage":"Must pass `secret` if not set to JWT getToken()","messagePattern":"Must pass `secret` if not set to JWT getToken\\(\\)","errorType":"exception","errorClass":"MissingSecret","httpStatus":null,"severity":"error","filePath":"packages/core/src/jwt.ts","lineNumber":191,"sourceCode":"\n  const authorizationHeader = headers.get(\"authorization\")\n\n  if (!token && authorizationHeader?.split(\" \")[0] === \"Bearer\") {\n    const urlEncodedToken = authorizationHeader.split(\" \")[1]\n    try {\n      token = decodeURIComponent(urlEncodedToken)\n    } catch {\n      // Malformed percent-encoding makes the Bearer token invalid\n      return null\n    }\n  }\n\n  if (!token) return null\n\n  if (raw) return token\n\n  if (!secret)\n    throw new MissingSecret(\"Must pass `secret` if not set to JWT getToken()\")\n\n  try {\n    return await _decode({ token, secret, salt })\n  } catch {\n    return null\n  }\n}\n\nasync function getDerivedEncryptionKey(\n  enc: string,\n  keyMaterial: Parameters<typeof hkdf>[1],\n  salt: Parameters<typeof hkdf>[2]\n) {\n  let length: number\n  switch (enc) {\n    case \"A256CBC-HS512\":\n      length = 64\n      break","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/nextauthjs/next-auth/blob/a1a16a5a7780488c7449feece410033f445d0b31/packages/core/src/jwt.ts#L173-L209","documentation":"Auth.js's getToken() decrypts/verifies the session JWT, which requires the same secret used to encode it. If no secret is available (not passed as an argument and not configured in the Auth config), it throws MissingSecret instead of guessing. This is thrown in packages/core/src/jwt.ts when getToken() is called with raw=false and no secret.","triggerScenarios":"Calling getToken({ req, raw: false }) (the default) without a secret argument while the Auth() config also has no secret set. Passing raw: true bypasses this because the raw token is returned without decoding.","commonSituations":"Reading the session in a route handler/middleware in a separate entry point that does not pass the AUTH_SECRET; deploying without the AUTH_SECRET environment variable; calling getToken outside the framework where options.secret was never propagated.","solutions":["Set the AUTH_SECRET environment variable (or pass secret in the Auth config) so getToken can pick it up.","Pass secret explicitly: getToken({ req, secret: process.env.AUTH_SECRET }).","If you only need the raw token string, call getToken({ req, raw: true }) which does not require a secret.","Verify the runtime actually loads .env files (e.g. next dev loads .env.local, but plain Node needs dotenv)."],"exampleFix":"// before\nconst token = await getToken({ req })\n// after\nconst token = await getToken({ req, secret: process.env.AUTH_SECRET })","handlingStrategy":"validation","validationCode":"if (!process.env.AUTH_SECRET) throw new Error('AUTH_SECRET must be set before calling getToken()')\nconst token = await getToken({ req, secret: process.env.AUTH_SECRET })","typeGuard":"function hasSecret(opts: { secret?: string }): opts is { secret: string } {\n  return typeof opts.secret === 'string' && opts.secret.length > 0\n}","tryCatchPattern":"try {\n  const token = await getToken({ req })\n} catch (e) {\n  if (e instanceof MissingSecret) {\n    // fall back to unauthenticated response or configure secret\n  }\n}","preventionTips":["Always set AUTH_SECRET in every environment (dev, CI, prod).","Pass secret explicitly to getToken in standalone route handlers and middleware.","Use raw: true when you only need the token string, avoiding decode entirely.","Add a startup check that fails fast when AUTH_SECRET is missing."],"tags":["jwt","config","missing-secret","authjs"],"backgroundTag":"missing-jwt-secret","analyzedSha":"a1a16a5a7780488c7449feece410033f445d0b31","analyzedAt":"2026-08-28T21:52:38.200Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}