Skip to content

Commit ffac5ca

Browse files
authored
Honor forwarded HTTPS for session cookies (#29)
1 parent 60b5f04 commit ffac5ca

4 files changed

Lines changed: 32 additions & 13 deletions

File tree

app/api/auth/login/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ export async function POST(req: NextRequest) {
2323
}
2424

2525
const res = NextResponse.json({ ok: true, status: account.status, plan: account.plan });
26-
res.cookies.set(SESSION_COOKIE, signSession({ sub: `acct:${account.id}`, email: account.email, name: null }), sessionCookieOptions());
26+
res.cookies.set(SESSION_COOKIE, signSession({ sub: `acct:${account.id}`, email: account.email, name: null }), sessionCookieOptions(req));
2727
return res;
2828
}

app/api/auth/signup/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export async function POST(req: NextRequest) {
7373
// Log the new account in immediately (as a pending account) so the dashboard
7474
// can show payment status.
7575
const setCookie = (res: NextResponse) => {
76-
res.cookies.set(SESSION_COOKIE, signSession({ sub: `acct:${account.id}`, email, name: null }), sessionCookieOptions());
76+
res.cookies.set(SESSION_COOKIE, signSession({ sub: `acct:${account.id}`, email, name: null }), sessionCookieOptions(req));
7777
return res;
7878
};
7979

app/auth/coinpay/callback/route.ts

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { NextRequest, NextResponse } from "next/server";
2-
import { coinpayConfigured, signSession, SESSION_COOKIE, SESSION_TTL } from "@/lib/session";
2+
import { coinpayConfigured, signSession, SESSION_COOKIE, sessionCookieOptions } from "@/lib/session";
33
import { exchangeCode, fetchUserinfo, APP_BASE_URL } from "@/lib/oauth";
44
import { upsertUser } from "@/lib/db";
55

@@ -29,10 +29,7 @@ export async function GET(req: NextRequest) {
2929
await upsertUser({ sub: info.sub, email: info.email, name: info.name });
3030

3131
const res = NextResponse.redirect(`${APP_BASE_URL}/`);
32-
const secure = APP_BASE_URL.startsWith("https://");
33-
res.cookies.set(SESSION_COOKIE, signSession({ sub: info.sub, email: info.email ?? null, name: info.name ?? null }), {
34-
httpOnly: true, sameSite: "lax", secure, path: "/", maxAge: SESSION_TTL,
35-
});
32+
res.cookies.set(SESSION_COOKIE, signSession({ sub: info.sub, email: info.email ?? null, name: info.name ?? null }), sessionCookieOptions(req));
3633
res.cookies.delete("cp_pkce");
3734
res.cookies.delete("cp_state");
3835
return res;

lib/session.ts

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,34 @@ export function signSession(payload: { sub: string; email: string | null; name:
2828

2929
/** Cookie options for the session cookie — secure only when served over https.
3030
* Detects TLS proxies (Vercel/Cloudflare/Railway) via x-forwarded-proto and honors NODE_ENV=production. */
31-
export function sessionCookieOptions() {
32-
const proto = (process.env.X_FORWARDED_PROTO || process.env.FORWARDED_PROTO || "").split(",")[0].trim().toLowerCase();
33-
const secure =
34-
(process.env.NODE_ENV === "production") ||
35-
(process.env.APP_BASE_URL || "").startsWith("https://") ||
36-
proto === "https";
31+
type CookieRequest = {
32+
headers?: Headers;
33+
nextUrl?: URL;
34+
url?: string;
35+
};
36+
37+
function requestIsHttps(req?: CookieRequest): boolean {
38+
const proto = (
39+
req?.headers?.get("x-forwarded-proto") ||
40+
process.env.X_FORWARDED_PROTO ||
41+
process.env.FORWARDED_PROTO ||
42+
""
43+
).split(",")[0].trim().toLowerCase();
44+
if (proto === "https") return true;
45+
if (proto === "http") return false;
46+
if (req?.nextUrl?.protocol === "https:") return true;
47+
if (req?.url) {
48+
try {
49+
return new URL(req.url).protocol === "https:";
50+
} catch {
51+
// Fall back to deployment env below.
52+
}
53+
}
54+
return (process.env.NODE_ENV === "production") || (process.env.APP_BASE_URL || "").startsWith("https://");
55+
}
56+
57+
export function sessionCookieOptions(req?: CookieRequest) {
58+
const secure = requestIsHttps(req);
3759
return { httpOnly: true, sameSite: "lax" as const, secure, path: "/", maxAge: SESSION_TTL };
3860
}
3961

0 commit comments

Comments
 (0)