Skip to content

Commit

Permalink
Update Next.js: Refactor to Asynchronous cookies() API (#1726)
Browse files Browse the repository at this point in the history
  • Loading branch information
ERmilburn02 authored Oct 28, 2024
1 parent 3bcdb1d commit e0914d3
Show file tree
Hide file tree
Showing 3 changed files with 28 additions and 17 deletions.
17 changes: 12 additions & 5 deletions pages/sessions/cookies/nextjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
const cookieStore = await cookies();
cookieStore.set("session", token, {
httpOnly: true,
sameSite: "lax",
secure: process.env.NODE_ENV === "production",
Expand All @@ -72,8 +73,9 @@ export function setSessionTokenCookie(token: string, expiresAt: Date): void {
});
}

export function deleteSessionTokenCookie(): void {
cookies().set("session", "", {
export async function deleteSessionTokenCookie(): Promise<void> {
const cookieStore = await cookies();
cookieStore.set("session", "", {
httpOnly: true,
sameSite: "lax",
secure: process.env.NODE_ENV === "production",
Expand All @@ -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.
Expand Down Expand Up @@ -154,7 +158,8 @@ import { cache } from "react";
// ...

export const getCurrentSession = cache(async (): Promise<SessionValidationResult> => {
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 };
}
Expand All @@ -163,6 +168,8 @@ export const getCurrentSession = cache(async (): Promise<SessionValidationResult
});
```

> 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
Expand Down
12 changes: 7 additions & 5 deletions pages/tutorials/github-oauth/nextjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ export async function GET(): Promise<Response> {
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,
Expand Down Expand Up @@ -117,7 +118,8 @@ export async function GET(request: Request): Promise<Response> {
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
Expand Down Expand Up @@ -153,7 +155,7 @@ export async function GET(request: Request): Promise<Response> {
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: {
Expand All @@ -167,7 +169,7 @@ export async function GET(request: Request): Promise<Response> {

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: {
Expand Down Expand Up @@ -221,7 +223,7 @@ async function logout(): Promise<ActionResult> {
}

await invalidateSession(session.id);
deleteSessionTokenCookie();
await deleteSessionTokenCookie();
return redirect("/login");
}

Expand Down
16 changes: 9 additions & 7 deletions pages/tutorials/google-oauth/nextjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,14 +83,15 @@ export async function GET(): Promise<Response> {
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",
Expand Down Expand Up @@ -123,8 +124,9 @@ export async function GET(request: Request): Promise<Response> {
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
Expand Down Expand Up @@ -155,7 +157,7 @@ export async function GET(request: Request): Promise<Response> {
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: {
Expand All @@ -169,7 +171,7 @@ export async function GET(request: Request): Promise<Response> {

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: {
Expand Down Expand Up @@ -223,7 +225,7 @@ async function logout(): Promise<ActionResult> {
}

await invalidateSession(session.id);
deleteSessionTokenCookie();
await deleteSessionTokenCookie();
return redirect("/login");
}

Expand Down

0 comments on commit e0914d3

Please sign in to comment.