Skip to content

Commit

Permalink
improve next.js guide
Browse files Browse the repository at this point in the history
  • Loading branch information
pilcrowonpaper committed Dec 7, 2023
1 parent 816e0b4 commit 43a9a80
Showing 1 changed file with 32 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,35 @@ layout: "@layouts/DocLayout.astro"
title: "Validate session cookies in Next.js App router"
---

Next.js throws an error when you attempt to set a cookie when rendering components, so we unfortunately have to write 2 different functions for validating requests. This is a known issue but Vercel has yet to acknowledge or fix the issue.
You can get the cookie name with `Lucia.sessionCookieName` and validate the session cookie with `Lucia.validateSession()`. Delete the session cookie if it's invalid and create a new session cookie when the expiration gets extended, which is indicated by `Session.fresh`. We have to wrap it inside a try/catch block since Next.js doesn't allow you to set cookies when rendering the page. This is a known issue but Vercel has yet to acknowledge or address the issue.

This also means you'll need to set `sessionCookie.expires` option to `false` so the session cookie persists for a long time.
We recommend wrapping the function with [`cache()`]() so it can be called multiple times without incurring multiple database calls.

```ts
import { lucia } from "@/utils/auth";
import { cookies } from "next/headers";

const getUser = cache(async () => {
const sessionId = cookies().get(lucia.sessionCookieName);
if (!sessionId) return null;
const { user, session } = await lucia.validateSession(sessionId);
try {
if (session && session.fresh) {
const sessionCookie = lucia.createSessionCookie(session.id);
cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
}
if (!session) {
const sessionCookie = lucia.createBlankSessionCookie();
cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
}
} catch {
// Next.js throws error when attempting to set cookies when rendering page
}
return user;
});
```

Set `sessionCookie.expires` option to `false` so the session cookie persists for a longer period.

```ts
import { Lucia } from "lucia";
Expand All @@ -21,69 +47,21 @@ const lucia = new Lucia(adapter, {
});
```

## Server components

You can get the cookie name with `Lucia.sessionCookieName` and validate the session cookie with `Lucia.validateSession()`. We recommend wrapping the function with [`cache()`]() so it can be called multiple times without incurring multiple database calls.

```ts
import { lucia } from "@/utils/auth";
import { cookies } from "next/headers";

const validatePageRequest = cache(async () => {
const sessionId = cookies().get(lucia.sessionCookieName);
if (!sessionId) return null;
const { user } = await lucia.validateSession(sessionId);
return user;
});
```
You can now use `getUser()` in server components, including server actions.

```ts
// app/api/page.tsx
import { redirect } from "next/navigation";

async function Page() {
const user = await validatePageRequest();
const user = await getUser();
if (!user) {
redirect("/login");
}
// ...
}
```

## Server actions and API routes

Make sure to delete the session cookie if it's invalid and create a new session cookie when the expiration gets extended, which is indicated by `Session.fresh`.

```ts
import { lucia } from "@/utils/auth";
import { cookies, headers } from "next/headers";

import type { User } from "lucia";

async function validateRequest(): Promise<User | null> {
const sessionId = cookies().get(lucia.sessionCookieName);
if (!sessionId) return null;
const { session, user } = await lucia.validateSession(sessionId);
if (session && session.fresh) {
const sessionCookie = lucia.createSessionCookie(session.id);
cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
}
if (!session) {
const sessionCookie = lucia.createBlankSessionCookie();
cookies().set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
}
return user;
}
```

```ts
// app/api/page.tsx
import { redirect } from "next/navigation";

async function Page() {
async function action() {
"use server";
const user = await validateRequest();
const user = await getUser();
if (!user) {
redirect("/login");
}
Expand All @@ -93,7 +71,7 @@ async function Page() {
}
```

Since Next.js do not implement CSRF protection for API routes, **CSRF protection must be implemented when dealing with forms** if you're dealing with forms. This can be easily done by comparing the `Origin` and `Host` header. You may want to add the CSRF protection inside middleware.
For API routes, since Next.js do not implement CSRF protection for API routes, **CSRF protection must be implemented when dealing with forms** if you're dealing with forms. This can be easily done by comparing the `Origin` and `Host` header. You may want to add the CSRF protection inside middleware.

```ts
// app/api/route.ts
Expand Down

0 comments on commit 43a9a80

Please sign in to comment.