Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 44 additions & 39 deletions docs/01-app/02-guides/authentication.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,7 @@ Session management ensures that the user's authenticated state is preserved acro
There are two types of sessions:

1. [**Stateless**](#stateless-sessions): Session data (or a token) is stored in the browser's cookies. The cookie is sent with each request, allowing the session to be verified on the server. This method is simpler, but can be less secure if not implemented correctly.
2. [**Database**](#database-sessions): Session data is stored in a database, with the user's browser only receiving the encrypted session ID. This method is more secure, but can be complex and use more server resources.
2. [**Database**](#database-sessions): Session data is stored in a database, with the user's browser only receiving a signed token that contains the session ID. This method is more secure, but can be complex and use more server resources.

> **Good to know:** While you can use either method, or both, we recommend using a session management library such as [iron-session](https://github.com/vvo/iron-session) or [Jose](https://github.com/panva/jose).

Expand All @@ -534,7 +534,7 @@ There are two types of sessions:
To create and manage stateless sessions, there are a few steps you need to follow:

1. Generate a secret key, which will be used to sign your session, and store it as an [environment variable](/docs/app/guides/environment-variables).
2. Write logic to encrypt/decrypt session data using a session management library.
2. Write logic to sign and verify session data using a session management library.
3. Manage cookies using the Next.js [`cookies`](/docs/app/api-reference/functions/cookies) API.

In addition to the above, consider adding functionality to [update (or refresh)](#updating-or-refreshing-sessions) the session when the user returns to the application, and [delete](#deleting-the-session) the session when the user logs out.
Expand All @@ -561,9 +561,11 @@ You can then reference this key in your session management logic:
const secretKey = process.env.SESSION_SECRET
```

#### 2. Encrypting and decrypting sessions
#### 2. Signing and verifying sessions

Next, you can use your preferred [session management library](#session-management-libraries) to encrypt and decrypt sessions. Continuing from the previous example, we'll use [Jose](https://www.npmjs.com/package/jose) and React's [`server-only`](https://www.npmjs.com/package/server-only) package to ensure that your session management logic is only executed on the server.
Next, you can use your preferred [session management library](#session-management-libraries) to sign and verify sessions. Continuing from the previous example, we'll use [Jose](https://www.npmjs.com/package/jose) and React's [`server-only`](https://www.npmjs.com/package/server-only) package to ensure that your session management logic is only executed on the server.

Signing prevents undetected changes to the session data, but it does not hide that data from the user. Do not include sensitive information in the session payload.

```tsx filename="app/lib/session.ts" switcher
import 'server-only'
Expand All @@ -573,15 +575,15 @@ import { SessionPayload } from '@/app/lib/definitions'
const secretKey = process.env.SESSION_SECRET
const encodedKey = new TextEncoder().encode(secretKey)

export async function encrypt(payload: SessionPayload) {
export async function sign(payload: SessionPayload) {
return new SignJWT(payload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('7d')
.sign(encodedKey)
}

export async function decrypt(session: string | undefined = '') {
export async function verify(session: string | undefined = '') {
try {
const { payload } = await jwtVerify(session, encodedKey, {
algorithms: ['HS256'],
Expand All @@ -600,15 +602,15 @@ import { SignJWT, jwtVerify } from 'jose'
const secretKey = process.env.SESSION_SECRET
const encodedKey = new TextEncoder().encode(secretKey)

export async function encrypt(payload) {
export async function sign(payload) {
return new SignJWT(payload)
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('7d')
.sign(encodedKey)
}

export async function decrypt(session) {
export async function verify(session) {
try {
const { payload } = await jwtVerify(session, encodedKey, {
algorithms: ['HS256'],
Expand Down Expand Up @@ -642,7 +644,7 @@ import { cookies } from 'next/headers'

export async function createSession(userId: string) {
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
const session = await encrypt({ userId, expiresAt })
const session = await sign({ userId, expiresAt })
const cookieStore = await cookies()

cookieStore.set('session', session, {
Expand All @@ -661,7 +663,7 @@ import { cookies } from 'next/headers'

export async function createSession(userId) {
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
const session = await encrypt({ userId, expiresAt })
const session = await sign({ userId, expiresAt })
const cookieStore = await cookies()

cookieStore.set('session', session, {
Expand Down Expand Up @@ -722,11 +724,11 @@ You can also extend the session's expiration time. This is useful for keeping th
```ts filename="app/lib/session.ts" switcher
import 'server-only'
import { cookies } from 'next/headers'
import { decrypt } from '@/app/lib/session'
import { verify } from '@/app/lib/session'

export async function updateSession() {
const session = (await cookies()).get('session')?.value
const payload = await decrypt(session)
const payload = await verify(session)

if (!session || !payload) {
return null
Expand All @@ -748,12 +750,12 @@ export async function updateSession() {
```js filename="app/lib/session.js" switcher
import 'server-only'
import { cookies } from 'next/headers'
import { decrypt } from '@/app/lib/session'
import { verify } from '@/app/lib/session'

export async function updateSession() {
const cookieStore = await cookies()
const session = cookieStore.get('session')?.value
const payload = await decrypt(session)
const payload = await verify(session)

if (!session || !payload) {
return null
Expand Down Expand Up @@ -829,13 +831,16 @@ You can use [API Routes](/docs/pages/building-your-application/routing/api-route
```ts filename="pages/api/login.ts" switcher
import { serialize } from 'cookie'
import type { NextApiRequest, NextApiResponse } from 'next'
import { encrypt } from '@/app/lib/session'
import { sign } from '@/app/lib/session'

export default function handler(req: NextApiRequest, res: NextApiResponse) {
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
const sessionData = req.body
const encryptedSessionData = encrypt(sessionData)
const signedSessionData = await sign(sessionData)

const cookie = serialize('session', encryptedSessionData, {
const cookie = serialize('session', signedSessionData, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 7, // One week
Expand All @@ -848,13 +853,13 @@ export default function handler(req: NextApiRequest, res: NextApiResponse) {

```js filename="pages/api/login.js" switcher
import { serialize } from 'cookie'
import { encrypt } from '@/app/lib/session'
import { sign } from '@/app/lib/session'

export default function handler(req, res) {
export default async function handler(req, res) {
const sessionData = req.body
const encryptedSessionData = encrypt(sessionData)
const signedSessionData = await sign(sessionData)

const cookie = serialize('session', encryptedSessionData, {
const cookie = serialize('session', signedSessionData, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
maxAge: 60 * 60 * 24 * 7, // One week
Expand All @@ -873,7 +878,7 @@ To create and manage database sessions, you'll need to follow these steps:

1. Create a table in your database to store session and data (or check if your Auth Library handles this).
2. Implement functionality to insert, update, and delete sessions
3. Encrypt the session ID before storing it in the user's browser, and ensure the database and cookie stay in sync (this is optional, but recommended for optimistic auth checks in [Proxy](#optimistic-checks-with-proxy-optional)).
3. Sign a session payload containing the session ID before storing it in the user's browser, and ensure the database and cookie stay in sync (this is optional, but recommended for optimistic auth checks in [Proxy](#optimistic-checks-with-proxy-optional)).

<AppOnly>

Expand All @@ -882,7 +887,7 @@ For example:
```ts filename="app/lib/session.ts" switcher
import { cookies } from 'next/headers'
import { db } from '@/app/lib/db'
import { encrypt } from '@/app/lib/session'
import { sign } from '@/app/lib/session'

export async function createSession(id: number) {
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
Expand All @@ -899,8 +904,8 @@ export async function createSession(id: number) {

const sessionId = data[0].id

// 2. Encrypt the session ID
const session = await encrypt({ sessionId, expiresAt })
// 2. Sign the session payload
const session = await sign({ sessionId, expiresAt })

// 3. Store the session in cookies for optimistic auth checks
const cookieStore = await cookies()
Expand All @@ -917,7 +922,7 @@ export async function createSession(id: number) {
```js filename="app/lib/session.js" switcher
import { cookies } from 'next/headers'
import { db } from '@/app/lib/db'
import { encrypt } from '@/app/lib/session'
import { sign } from '@/app/lib/session'

export async function createSession(id) {
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)
Expand All @@ -934,8 +939,8 @@ export async function createSession(id) {

const sessionId = data[0].id

// 2. Encrypt the session ID
const session = await encrypt({ sessionId, expiresAt })
// 2. Sign the session payload
const session = await sign({ sessionId, expiresAt })

// 3. Store the session in cookies for optimistic auth checks
const cookieStore = await cookies()
Expand Down Expand Up @@ -1036,7 +1041,7 @@ For example:

```tsx filename="proxy.ts" switcher
import { NextRequest, NextResponse } from 'next/server'
import { decrypt } from '@/app/lib/session'
import { verify } from '@/app/lib/session'
import { cookies } from 'next/headers'

// 1. Specify protected and public routes
Expand All @@ -1049,9 +1054,9 @@ export default async function proxy(req: NextRequest) {
const isProtectedRoute = protectedRoutes.includes(path)
const isPublicRoute = publicRoutes.includes(path)

// 3. Decrypt the session from the cookie
// 3. Verify the session from the cookie
const cookie = (await cookies()).get('session')?.value
const session = await decrypt(cookie)
const session = await verify(cookie)

// 4. Redirect to /login if the user is not authenticated
if (isProtectedRoute && !session?.userId) {
Expand All @@ -1078,7 +1083,7 @@ export const config = {

```js filename="proxy.js" switcher
import { NextResponse } from 'next/server'
import { decrypt } from '@/app/lib/session'
import { verify } from '@/app/lib/session'
import { cookies } from 'next/headers'

// 1. Specify protected and public routes
Expand All @@ -1091,9 +1096,9 @@ export default async function proxy(req) {
const isProtectedRoute = protectedRoutes.includes(path)
const isPublicRoute = publicRoutes.includes(path)

// 3. Decrypt the session from the cookie
// 3. Verify the session from the cookie
const cookie = (await cookies()).get('session')?.value
const session = await decrypt(cookie)
const session = await verify(cookie)

// 5. Redirect to /login if the user is not authenticated
if (isProtectedRoute && !session?.userId) {
Expand Down Expand Up @@ -1140,11 +1145,11 @@ For example, create a separate file for your DAL that includes a `verifySession(
import 'server-only'

import { cookies } from 'next/headers'
import { decrypt } from '@/app/lib/session'
import { verify } from '@/app/lib/session'

export const verifySession = cache(async () => {
const cookie = (await cookies()).get('session')?.value
const session = await decrypt(cookie)
const session = await verify(cookie)

if (!session?.userId) {
redirect('/login')
Expand All @@ -1158,11 +1163,11 @@ export const verifySession = cache(async () => {
import 'server-only'

import { cookies } from 'next/headers'
import { decrypt } from '@/app/lib/session'
import { verify } from '@/app/lib/session'

export const verifySession = cache(async () => {
const cookie = (await cookies()).get('session')?.value
const session = await decrypt(cookie)
const session = await verify(cookie)

if (!session.userId) {
redirect('/login')
Expand Down