From edcf8a0c273bed2d1b20dc0d1a7cf535c57e57c3 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sun, 18 Jan 2026 21:39:57 +0100 Subject: [PATCH] fix: set cookie domain to .ciphera.net for cross-subdomain auth --- app/actions/auth.ts | 30 ++++++++++++++++++++++++++++-- app/api/auth/refresh/route.ts | 16 ++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/app/actions/auth.ts b/app/actions/auth.ts index fca3de3..4d4f8b0 100644 --- a/app/actions/auth.ts +++ b/app/actions/auth.ts @@ -4,6 +4,16 @@ import { cookies } from 'next/headers' const AUTH_API_URL = process.env.NEXT_PUBLIC_AUTH_API_URL || process.env.NEXT_PUBLIC_AUTH_URL || 'http://localhost:8081' +// * Determine cookie domain dynamically +// * In production (on ciphera.net), we want to share cookies with subdomains (e.g. analytics-api.ciphera.net) +// * In local dev (localhost), we don't set a domain +const getCookieDomain = () => { + if (process.env.NODE_ENV === 'production') { + return '.ciphera.net' + } + return undefined +} + interface AuthResponse { access_token: string refresh_token: string @@ -46,6 +56,7 @@ export async function exchangeAuthCode(code: string, codeVerifier: string, redir // * Set Cookies const cookieStore = await cookies() + const cookieDomain = getCookieDomain() // * Access Token cookieStore.set('access_token', data.access_token, { @@ -53,6 +64,7 @@ export async function exchangeAuthCode(code: string, codeVerifier: string, redir secure: process.env.NODE_ENV === 'production', sameSite: 'lax', path: '/', + domain: cookieDomain, maxAge: 60 * 15 // 15 minutes (short lived) }) @@ -62,6 +74,7 @@ export async function exchangeAuthCode(code: string, codeVerifier: string, redir secure: process.env.NODE_ENV === 'production', sameSite: 'lax', path: '/', + domain: cookieDomain, maxAge: 60 * 60 * 24 * 30 // 30 days }) @@ -86,12 +99,14 @@ export async function setSessionAction(accessToken: string, refreshToken: string const payload: UserPayload = JSON.parse(Buffer.from(payloadPart, 'base64').toString()) const cookieStore = await cookies() + const cookieDomain = getCookieDomain() cookieStore.set('access_token', accessToken, { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax', path: '/', + domain: cookieDomain, maxAge: 60 * 15 }) @@ -100,6 +115,7 @@ export async function setSessionAction(accessToken: string, refreshToken: string secure: process.env.NODE_ENV === 'production', sameSite: 'lax', path: '/', + domain: cookieDomain, maxAge: 60 * 60 * 24 * 30 }) @@ -118,8 +134,18 @@ export async function setSessionAction(accessToken: string, refreshToken: string export async function logoutAction() { const cookieStore = await cookies() - cookieStore.delete('access_token') - cookieStore.delete('refresh_token') + const cookieDomain = getCookieDomain() + + cookieStore.set('access_token', '', { + maxAge: 0, + path: '/', + domain: cookieDomain + }) + cookieStore.set('refresh_token', '', { + maxAge: 0, + path: '/', + domain: cookieDomain + }) return { success: true } } diff --git a/app/api/auth/refresh/route.ts b/app/api/auth/refresh/route.ts index 363b2c4..09894a3 100644 --- a/app/api/auth/refresh/route.ts +++ b/app/api/auth/refresh/route.ts @@ -3,6 +3,14 @@ import { NextResponse } from 'next/server' const AUTH_API_URL = process.env.NEXT_PUBLIC_AUTH_API_URL || process.env.NEXT_PUBLIC_AUTH_URL || 'http://localhost:8081' +// * Determine cookie domain dynamically +const getCookieDomain = () => { + if (process.env.NODE_ENV === 'production') { + return '.ciphera.net' + } + return undefined +} + export async function POST() { const cookieStore = await cookies() const refreshToken = cookieStore.get('refresh_token')?.value @@ -18,10 +26,12 @@ export async function POST() { body: JSON.stringify({ refresh_token: refreshToken }), }) + const cookieDomain = getCookieDomain() + if (!res.ok) { // * If refresh fails, clear cookies - cookieStore.delete('access_token') - cookieStore.delete('refresh_token') + cookieStore.set('access_token', '', { maxAge: 0, path: '/', domain: cookieDomain }) + cookieStore.set('refresh_token', '', { maxAge: 0, path: '/', domain: cookieDomain }) return NextResponse.json({ error: 'Refresh failed' }, { status: 401 }) } @@ -32,6 +42,7 @@ export async function POST() { secure: process.env.NODE_ENV === 'production', sameSite: 'lax', path: '/', + domain: cookieDomain, maxAge: 60 * 15 }) @@ -40,6 +51,7 @@ export async function POST() { secure: process.env.NODE_ENV === 'production', sameSite: 'lax', path: '/', + domain: cookieDomain, maxAge: 60 * 60 * 24 * 30 })