import 'server-only'; import crypto from 'crypto'; import { cookies } from 'next/headers'; import { getAuthCookieName, getAuthCookieOptions } from './cookieConfig'; /** * Signed session cookie. * * The auth cookie holds the user id, but it MUST NOT be a bare, forgeable value. * We attach an HMAC-SHA256 signature keyed with NEXTAUTH_SECRET so the server can * detect a tampered/forged cookie and reject it. Format: `.`. */ function getSecret(): string { const secret = process.env.NEXTAUTH_SECRET; if (!secret) { throw new Error('NEXTAUTH_SECRET is not set - cannot sign or verify session cookies'); } return secret; } function computeSignature(userId: string): string { return crypto.createHmac('sha256', getSecret()).update(userId).digest('base64url'); } /** * Produce the signed cookie value for a user id. */ export function signUserId(userId: string): string { return `${userId}.${computeSignature(userId)}`; } /** * Verify a signed cookie value. Returns the user id if the signature is valid, * otherwise null. Uses a constant-time comparison to avoid signature timing leaks. */ export function verifySignedUserId(value: string | undefined | null): string | null { if (!value) return null; const separator = value.lastIndexOf('.'); if (separator <= 0 || separator === value.length - 1) { return null; } const userId = value.slice(0, separator); const providedSig = value.slice(separator + 1); const expectedSig = computeSignature(userId); const providedBuf = Buffer.from(providedSig); const expectedBuf = Buffer.from(expectedSig); if (providedBuf.length !== expectedBuf.length) { return null; } if (!crypto.timingSafeEqual(providedBuf, expectedBuf)) { return null; } return userId; } /** * Read and verify the authenticated user id from the request cookies. * Returns null when no valid, correctly-signed session cookie is present. * * Use this in route handlers instead of reading the `userId` cookie directly. */ export function getSessionUserId(): string | null { return verifySignedUserId(cookies().get(getAuthCookieName())?.value); } /** * Set the signed auth cookie for the given user id (server component / route handler context). */ export function setSessionCookie(userId: string): void { cookies().set(getAuthCookieName(), signUserId(userId), getAuthCookieOptions()); }