/** * Edge-runtime variant of the signed-session verification used by middleware. * * Next.js middleware runs on the Edge runtime, where Node's `crypto` module is * unavailable, so we verify the HMAC signature with Web Crypto (SubtleCrypto). * Keep this in sync with `src/lib/session.ts` (same secret, same algorithm). */ function base64url(bytes: ArrayBuffer): string { const view = new Uint8Array(bytes); let bin = ''; for (let i = 0; i < view.length; i++) { bin += String.fromCharCode(view[i]); } return btoa(bin).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); } function timingSafeEqual(a: string, b: string): boolean { if (a.length !== b.length) return false; let diff = 0; for (let i = 0; i < a.length; i++) { diff |= a.charCodeAt(i) ^ b.charCodeAt(i); } return diff === 0; } /** * Verify a signed cookie value in the Edge runtime. * Returns the user id when the signature is valid, otherwise null. */ export async function verifySignedUserIdEdge( value: string | undefined | null ): Promise { if (!value) return null; const secret = process.env.NEXTAUTH_SECRET; if (!secret) 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 key = await crypto.subtle.importKey( 'raw', new TextEncoder().encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'] ); const signature = await crypto.subtle.sign('HMAC', key, new TextEncoder().encode(userId)); const expectedSig = base64url(signature); if (!timingSafeEqual(providedSig, expectedSig)) { return null; } return userId; }