iron-session
A runtime-agnostic stateless session utility. Works with Next.js, Node.js, Deno,
Bun, and more.
The session data is stored in signed and encrypted cookies ("seals") which can
only be decoded by your server. There are no session ids, making sessions
"stateless" from the server point of view. This strategy of storing session data
is the same technique used by frameworks like
Ruby On Rails.
Table of Contents
Installation
npm add iron-session
Change the package manager to whatever you use, of course. On Deno, you can use
esm.sh:
import { getIronSession } from 'https://esm.sh/iron-session@latest'
Usage
Refer to the examples.
- Define your session options
- Initialize your session with:
- sessionOptions and the respective parameters
- the type definition of your session data
- Set the session data variables
- Set the data to or read the data from the browser cookie storage
- session.save(): Set the session variables as an encrypted string to the browser cookie storage
- session.destroy(): Set cookie value in the browser cookie storage as an empty value to clear the cookie data
- Read the session variables by decrypting the encrypted string from the cookie browser storage
Options Definitions
Only two options are required: password
and cookieName
. Everything else is automatically computed and usually doesn't need to be changed.
password
, required: Private key used to encrypt the cookie. It has to be at least 32 characters long. Use https://1password.com/password-generator/ to generate strong passwords. password
can be either a string
or an array
of objects like this: [{id: 2, password: "..."}, {id: 1, password: "..."}]
to allow for password rotation.cookieName
, required: Name of the cookie to be storedttl
, optional: In seconds. Default to the equivalent of 14 days. You can set this to 0
and iron-session will compute the maximum allowed value by cookies (~70 years).cookieOptions
, optional: Any option available from jshttp/cookie#serialize except for encode
which is not a Set-Cookie Attribute. See Mozilla Set-Cookie Attributes and Chrome Cookie Fields. Default to:
{
httpOnly: true,
secure: true,
sameSite: "lax",
maxAge: (ttl === 0 ? 2147483647 : ttl) - 60,
path: "/",
}
The final type definition for CookieOptions
ends up to be
'domain' | 'path' | 'secure' | 'sameSite' | 'name' | 'value' | 'expires' | 'httpOnly' | 'maxAge' | 'priority'
Type Definitions from iron-session/dist/index.node.d.cts
IronSessionOptions
interface IronSessionOptions {
cookieName: string;
password: Password;
ttl?: number;
cookieOptions?: CookieOptions;
}
CookieOptions
type CookieOptions = Omit<CookieSerializeOptions, 'encode'>
CookieSerializeOptions
interface CookieSerializeOptions {
domain?: string | undefined;
encode?(value: string): string;
expires?: Date | undefined;
httpOnly?: boolean | undefined;
maxAge?: number | undefined;
path?: string | undefined;
priority?: 'low' | 'medium' | 'high' | undefined;
sameSite?: true | false | 'lax' | 'strict' | 'none' | undefined;
secure?: boolean | undefined;
}
API
Iron Session Object
getIronSession(req: Request | IncomingMessage, res: Response | ServerResponse, userSessionOptions: IronSessionOptions): Promise<IronSession>
const session = getIronSession<IronSessionData>(req, res, sessionOptions)
The API Route Handler that uses getIronSession
and returns the Response needs to be called from a client-side environment (ie. a 'use client' file).
getServerActionIronSession(userSessionOptions: IronSessionOptions, cookieHandler: ICookieHandler): Promise<IronSession>
const session = getServerActionIronSession<IronSessionData>(sessionOptions, cookies())
The getServerActionIronSession
implementation uses the cookies()
function from next/headers to set the cookies so that Iron Session can be used in NextJS Server Actions and React Server Components in a server-side environment (ie. a 'use server' file).
Iron Session Functions
session.save(saveOptions?: OverridableOptions)
Saves the session and sets the cookie header to be sent once the response is sent.
await session.save()
session.destroy(destroyOptions?: OverridableOptions)
Empties the session object and sets the cookie header to be sent once the response is sent. The browser will then set the cookie value as an empty value.
await session.destroy()
Upon calling either session.save()
or session.destroy()
the session values are saved to the browser cookie storage.
Iron Session Options
Default Options
const defaultOptions: Required<OverridableOptions> = {
ttl: fourteenDaysInSeconds,
cookieOptions: { httpOnly: true, secure: true, sameSite: 'lax', path: '/' },
}
User Session Options
You may apply options during the Iron Session object initialization. These options will superseded and override any options set in Default Options. For example: refer to cookieOptions
in lib/session.ts
in the below NextJS Example.
Override Options
You may apply options during the .save()
or .destroy()
function calls. These options will superseded and override any options set in Default Options and User Session Options.
For example:
type OverridableOptions = {
ttl?: number;
cookieOptions?: CookieOptions;
}
await session.save({ cookieOptions: { priority: 'high'} })
NextJS Example
lib/session.ts
import {
IronSessionOptions, getIronSession, IronSessionData, getServerActionIronSession
} from 'iron-session'
import { cookies } from 'next/headers';
export const sessionOptions: IronSessionOptions = {
password: 'change-this-this-is-not-a-secure-password',
cookieName: 'cookieNameInBrowser',
cookieOptions: {
secure: process.env.NODE_ENV === 'production',
},
}
declare module 'iron-session' {
interface IronSessionData {
cookieVariable?: string;
}
}
const getSession = async (req: Request, res: Response) => {
const session = getIronSession<IronSessionData>(req, res, sessionOptions)
return session
}
const getServerActionSession = async () => {
const session = getServerActionIronSession<IronSessionData>(sessionOptions, cookies())
return session
}
export {
getSession,
getServerActionSession
}
getIronSession
src/app/clientActions.ts
'use client'
export const submitCookieToStorageRouteHandler = async (cookie: string) => {
await fetch('http://localhost:3000/api/submitIronSessionCookie', {
method: 'POST',
body: JSON.stringify({
cookie,
}),
headers: {
'Content-Type': 'application/json',
},
})
}
export const readCookieFromStorageRouteHandler = async (): Promise<string> => {
const responseWithCookieFromStorage = await fetch('http://localhost:3000/api/readIronSessionCookie', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
const data = await responseWithCookieFromStorage.json();
const cookieValue = data?.cookieInStorage || 'No Cookie In Storage'
return cookieValue
}
src/api/submitIronSessionCookie/route.ts
import { getSession } from '../../../../lib/session'
export async function POST(request: Request) {
try {
const requestBody = await request.json()
const { cookie }: { cookie: string } = requestBody
const response = new Response()
const session = await getSession(request, response)
session.cookieVariable = cookie
await session.save()
return response
} catch (error: unknown) {
console.error((error as Error).message)
return new Response(JSON.stringify({ message: (error as Error).message }), { status: 500 })
}
}
src/api/readIronSessionCookie/route.ts
import { NextResponse } from 'next/server'
import { getSession } from '../../../../lib/session'
export async function GET(request: Request, response: Response) {
try {
const session = await getSession(request, response)
const cookeValue = session.cookieVariable || 'No Cookie Stored!'
return NextResponse.json({ cookieInStorage: cookeValue })
} catch (error: unknown) {
console.error((error as Error).message)
return new Response(JSON.stringify({ message: (error as Error).message }), { status: 500 })
}
}
getServerActionIronSession
src/app/serverActions.ts
'use server'
import { getServerActionSession } from '../../lib/session'
export const submitCookieToStorageServerAction = async (cookie: string) => {
const session = await getServerActionSession()
session.cookieVariable = cookie
await session.save()
}
export const readCookieFromStorageServerAction = async (): Promise<string> => {
const session = await getServerActionSession()
return session.cookieVariable || 'No Cookie Stored!'
}
next.config.js
const nextConfig = {
experimental: {
serverActions: true,
},
}
module.exports = nextConfig
FAQ
When should I use getIronSession or getServerActionIronSession?
Use getIronSession
when you wish to use Iron Session in a client-side environment with API Route Handlers and use getServerActionIronSession
when you wish to use Iron Session in a server-side environment with Server Components.
For NextJS projects using App Router with Server Actions enabled in their next.config.js
file, using getServerActionIronSession
is preferable for two reasons:
- allows Iron Session to be called and used from a server-side environment
- allows for more concise code. Server Actions can be called directly from your components without the need for a manually created API route. You can see the smaller amount of code used for
getServerActionIronSession
compared to getIronSession
in the example.
Credits
Good Reads