diff --git a/pages/sessions/basic-api/drizzle-orm.md b/pages/sessions/basic-api/drizzle-orm.md index da4484b73..aad2acb11 100644 --- a/pages/sessions/basic-api/drizzle-orm.md +++ b/pages/sessions/basic-api/drizzle-orm.md @@ -166,7 +166,7 @@ import { sha256 } from "@oslojs/crypto/sha2"; // ... -export async function createSession(token: string, userId: number): Session { +export async function createSession(token: string, userId: number): Promise { const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); const session: Session = { id: sessionId, @@ -253,7 +253,7 @@ export function generateSessionToken(): string { return token; } -export async function createSession(token: string, userId: number): Session { +export async function createSession(token: string, userId: number): Promise { const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); const session: Session = { id: sessionId, diff --git a/pages/sessions/basic-api/mysql.md b/pages/sessions/basic-api/mysql.md index 25597f65d..a5fbdc707 100644 --- a/pages/sessions/basic-api/mysql.md +++ b/pages/sessions/basic-api/mysql.md @@ -103,7 +103,7 @@ import { sha256 } from "@oslojs/crypto/sha2"; // ... -export async function createSession(token: string, userId: number): Session { +export async function createSession(token: string, userId: number): Promise { const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); const session: Session = { id: sessionId, @@ -195,7 +195,7 @@ export function generateSessionToken(): string { return token; } -export async function createSession(token: string, userId: number): Session { +export async function createSession(token: string, userId: number): Promise { const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); const session: Session = { id: sessionId, diff --git a/pages/sessions/basic-api/postgresql.md b/pages/sessions/basic-api/postgresql.md index 28ed37e4a..dd91e4d7c 100644 --- a/pages/sessions/basic-api/postgresql.md +++ b/pages/sessions/basic-api/postgresql.md @@ -103,7 +103,7 @@ import { sha256 } from "@oslojs/crypto/sha2"; // ... -export async function createSession(token: string, userId: number): Session { +export async function createSession(token: string, userId: number): Promise { const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); const session: Session = { id: sessionId, diff --git a/pages/sessions/basic-api/prisma.md b/pages/sessions/basic-api/prisma.md index 94fd824f0..4056e7fc1 100644 --- a/pages/sessions/basic-api/prisma.md +++ b/pages/sessions/basic-api/prisma.md @@ -95,7 +95,7 @@ import { sha256 } from "@oslojs/crypto/sha2"; // ... -export async function createSession(token: string, userId: number): Session { +export async function createSession(token: string, userId: number): Promise { const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); const session: Session = { id: sessionId, @@ -186,7 +186,7 @@ export function generateSessionToken(): string { return token; } -export async function createSession(token: string, userId: number): Session { +export async function createSession(token: string, userId: number): Promise { const sessionId = encodeHexLowerCase(sha256(new TextEncoder().encode(token))); const session: Session = { id: sessionId, diff --git a/pages/sessions/cookies/astro.md b/pages/sessions/cookies/astro.md index ff9d9865b..5bbf8a5dd 100644 --- a/pages/sessions/cookies/astro.md +++ b/pages/sessions/cookies/astro.md @@ -15,7 +15,7 @@ CSRF protection is a must when using cookies. From Astro v5.0, basic CSRF protec export default defineConfig({ output: "server", security: { - checkOrigin: false + checkOrigin: true } }); ``` diff --git a/pages/sessions/cookies/nextjs.md b/pages/sessions/cookies/nextjs.md index 3b6025533..a0aa2f7e0 100644 --- a/pages/sessions/cookies/nextjs.md +++ b/pages/sessions/cookies/nextjs.md @@ -62,8 +62,9 @@ import { cookies } from "next/headers"; // ... -export function setSessionTokenCookie(token: string, expiresAt: Date): void { - cookies().set("session", token, { +export async function setSessionTokenCookie(token: string, expiresAt: Date): Promise { + const cookieStore = await cookies(); + cookieStore.set("session", token, { httpOnly: true, sameSite: "lax", secure: process.env.NODE_ENV === "production", @@ -72,8 +73,9 @@ export function setSessionTokenCookie(token: string, expiresAt: Date): void { }); } -export function deleteSessionTokenCookie(): void { - cookies().set("session", "", { +export async function deleteSessionTokenCookie(): Promise { + const cookieStore = await cookies(); + cookieStore.set("session", "", { httpOnly: true, sameSite: "lax", secure: process.env.NODE_ENV === "production", @@ -83,6 +85,8 @@ export function deleteSessionTokenCookie(): void { } ``` +> Before Next.js 15, `cookies()` was synchronous. If you are using an older version, you should replace `await cookies()` with `cookies()`. You should also switch the function return type to `void`, and remove the `async` keyword. + Since we can't extend set cookies insides server components due to a limitation with React, we recommend continuously extending the cookie expiration inside middleware. However, this comes with its own issue. We can't detect if a new cookie was set inside server actions or route handlers from middleware. This becomes an issue if we need to assign a new session inside server actions (e.g. after updating the password) as the middleware cookie will override it. As such, we'll only extend the cookie expiration on GET requests. > While Lucia v3 recommended setup extended session cookie lifetime, it did not avoid the revalidation issue. @@ -154,7 +158,8 @@ import { cache } from "react"; // ... export const getCurrentSession = cache(async (): Promise => { - const token = cookies().get("session")?.value ?? null; + const cookieStore = await cookies(); + const token = cookieStore.get("session")?.value ?? null; if (token === null) { return { session: null, user: null }; } @@ -163,6 +168,8 @@ export const getCurrentSession = cache(async (): Promise On versions of Next.js below 15, replace `await cookies()` with `cookies()`. + This function can be used in server components, server actions, and route handlers (but importantly not middleware). ```ts diff --git a/pages/tutorials/github-oauth/nextjs.md b/pages/tutorials/github-oauth/nextjs.md index e2d7b18db..d49a0bd32 100644 --- a/pages/tutorials/github-oauth/nextjs.md +++ b/pages/tutorials/github-oauth/nextjs.md @@ -84,7 +84,8 @@ export async function GET(): Promise { const state = generateState(); const url = github.createAuthorizationURL(state, []); - cookies().set("github_oauth_state", state, { + const cookieStore = await cookies(); + cookieStore.set("github_oauth_state", state, { path: "/", secure: process.env.NODE_ENV === "production", httpOnly: true, @@ -117,7 +118,8 @@ export async function GET(request: Request): Promise { const url = new URL(request.url); const code = url.searchParams.get("code"); const state = url.searchParams.get("state"); - const storedState = cookies().get("github_oauth_state")?.value ?? null; + const cookieStore = await cookies(); + const storedState = cookieStore.get("github_oauth_state")?.value ?? null; if (code === null || state === null || storedState === null) { return new Response(null, { status: 400 @@ -153,7 +155,7 @@ export async function GET(request: Request): Promise { if (existingUser !== null) { const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, existingUser.id); - setSessionTokenCookie(sessionToken, session.expiresAt); + await setSessionTokenCookie(sessionToken, session.expiresAt); return new Response(null, { status: 302, headers: { @@ -167,7 +169,7 @@ export async function GET(request: Request): Promise { const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, user.id); - setSessionTokenCookie(sessionToken, session.expiresAt); + await setSessionTokenCookie(sessionToken, session.expiresAt); return new Response(null, { status: 302, headers: { @@ -221,7 +223,7 @@ async function logout(): Promise { } await invalidateSession(session.id); - deleteSessionTokenCookie(); + await deleteSessionTokenCookie(); return redirect("/login"); } diff --git a/pages/tutorials/google-oauth/nextjs.md b/pages/tutorials/google-oauth/nextjs.md index 0907263fc..0ed6cdcb6 100644 --- a/pages/tutorials/google-oauth/nextjs.md +++ b/pages/tutorials/google-oauth/nextjs.md @@ -83,14 +83,15 @@ export async function GET(): Promise { const codeVerifier = generateCodeVerifier(); const url = google.createAuthorizationURL(state, codeVerifier, ["openid", "profile"]); - cookies().set("google_oauth_state", state, { + const cookieStore = await cookies(); + cookieStore.set("google_oauth_state", state, { path: "/", httpOnly: true, secure: process.env.NODE_ENV === "production", maxAge: 60 * 10, // 10 minutes sameSite: "lax" }); - cookies().set("google_code_verifier", codeVerifier, { + cookieStore.set("google_code_verifier", codeVerifier, { path: "/", httpOnly: true, secure: process.env.NODE_ENV === "production", @@ -123,8 +124,9 @@ export async function GET(request: Request): Promise { const url = new URL(request.url); const code = url.searchParams.get("code"); const state = url.searchParams.get("state"); - const storedState = cookies().get("google_oauth_state")?.value ?? null; - const codeVerifier = cookies().get("google_code_verifier")?.value ?? null; + const cookieStore = await cookies(); + const storedState = cookieStore.get("google_oauth_state")?.value ?? null; + const codeVerifier = cookieStore.get("google_code_verifier")?.value ?? null; if (code === null || state === null || storedState === null || codeVerifier === null) { return new Response(null, { status: 400 @@ -155,7 +157,7 @@ export async function GET(request: Request): Promise { if (existingUser !== null) { const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, existingUser.id); - setSessionTokenCookie(sessionToken, session.expiresAt); + await setSessionTokenCookie(sessionToken, session.expiresAt); return new Response(null, { status: 302, headers: { @@ -169,7 +171,7 @@ export async function GET(request: Request): Promise { const sessionToken = generateSessionToken(); const session = await createSession(sessionToken, user.id); - setSessionTokenCookie(sessionToken, session.expiresAt); + await setSessionTokenCookie(sessionToken, session.expiresAt); return new Response(null, { status: 302, headers: { @@ -223,7 +225,7 @@ async function logout(): Promise { } await invalidateSession(session.id); - deleteSessionTokenCookie(); + await deleteSessionTokenCookie(); return redirect("/login"); } diff --git a/pages/tutorials/google-oauth/sveltekit.md b/pages/tutorials/google-oauth/sveltekit.md index a7c6d931b..63637032e 100644 --- a/pages/tutorials/google-oauth/sveltekit.md +++ b/pages/tutorials/google-oauth/sveltekit.md @@ -110,6 +110,7 @@ Create an API route in `routes/login/google/callback/+server.ts` to handle the c // routes/login/google/callback/+server.ts import { generateSessionToken, createSession, setSessionTokenCookie } from "$lib/server/session"; import { google } from "$lib/server/oauth"; +import { decodeIdToken } from "arctic"; import type { RequestEvent } from "@sveltejs/kit"; import type { OAuth2Tokens } from "arctic";