Secure, stateless, and cookie-based session library for JavaScript
npx @tessl/cli install tessl/npm-iron-session@8.0.0iron-session is a secure, stateless, and cookie-based session library for JavaScript that provides encrypted session storage using signed cookies with @hapi/iron cryptography, eliminating the need for server-side session storage or external services.
npm install iron-session or pnpm add iron-sessionimport { getIronSession, sealData, unsealData, type IronSession, type SessionOptions } from "iron-session";For CommonJS:
const { getIronSession, sealData, unsealData } = require("iron-session");import { getIronSession } from "iron-session";
// Next.js API Routes / Node.js Express
export async function handler(req, res) {
const session = await getIronSession(req, res, {
password: "complex_password_at_least_32_characters_long",
cookieName: "myapp_cookiename",
});
// Read session data
console.log(session.username);
// Set session data
session.username = "john_doe";
session.isLoggedIn = true;
// Save the session
await session.save();
}
// Next.js App Router (Route Handlers, Server Components, Server Actions)
import { cookies } from "next/headers";
export async function GET() {
const session = await getIronSession(cookies(), {
password: "complex_password_at_least_32_characters_long",
cookieName: "myapp_cookiename",
});
return Response.json({ username: session.username });
}iron-session is built around several key components:
Core session functionality for creating, reading, updating, and destroying encrypted cookie-based sessions.
/**
* Creates an encrypted session from HTTP request/response objects (Node.js/Express)
*/
function getIronSession<T extends object>(
req: IncomingMessage | Request,
res: Response | ServerResponse,
sessionOptions: SessionOptions
): Promise<IronSession<T>>;
/**
* Creates an encrypted session from Next.js cookie store (App Router)
*/
function getIronSession<T extends object>(
cookies: CookieStore,
sessionOptions: SessionOptions
): Promise<IronSession<T>>;Direct encryption utilities for creating secure tokens and magic links without session cookies.
/**
* Encrypts arbitrary data into a signed seal string
*/
function sealData(
data: unknown,
options: { password: Password; ttl?: number }
): Promise<string>;
/**
* Decrypts a seal string back to original data
*/
function unsealData<T>(
seal: string,
options: { password: Password; ttl?: number }
): Promise<T>;Usage Examples:
// Create a magic login link
const userId = 123;
const seal = await sealData(
{ userId, action: "login" },
{ password: "your_password", ttl: 3600 } // 1 hour
);
const magicLink = `https://yourapp.com/magic-login?token=${seal}`;
// Verify and use the magic link
const data = await unsealData(tokenFromUrl, {
password: "your_password",
ttl: 3600
});
console.log(data.userId); // 123import type { IncomingMessage, ServerResponse } from "http";
import type { CookieSerializeOptions } from "cookie";type IronSession<T> = T & {
/**
* Encrypts the session data and sets the cookie.
*/
readonly save: () => Promise<void>;
/**
* Destroys the session data and removes the cookie.
*/
readonly destroy: () => void;
/**
* Update the session configuration. You still need to call save() to send the new cookie.
*/
readonly updateConfig: (newSessionOptions: SessionOptions) => void;
};interface SessionOptions {
/**
* The cookie name that will be used inside the browser.
* Make sure it's unique given your application.
*/
cookieName: string;
/**
* The password(s) that will be used to encrypt the cookie.
* Must be at least 32 characters long.
*
* For password rotation, use an object with incrementing keys:
* { 1: 'old-password', 2: 'new-password' }
* The highest numbered key is used for new cookies.
*/
password: Password;
/**
* Session validity time in seconds. Default: 1209600 (14 days)
* ttl = 0 means no expiration
*/
ttl?: number;
/**
* Additional cookie configuration options.
* For session cookies (deleted when browser closes), use: { maxAge: undefined }
*/
cookieOptions?: CookieOptions;
}
/**
* Cookie store interface for Next.js cookies() API
*/
interface CookieStore {
get: (name: string) => { name: string; value: string } | undefined;
set: {
(name: string, value: string, cookie?: Partial<ResponseCookie>): void;
(options: ResponseCookie): void;
};
}
/**
* W3C CookieListItem specification with additional response properties
*/
interface ResponseCookie extends CookieListItem {
httpOnly?: boolean;
maxAge?: number;
priority?: 'low' | 'medium' | 'high';
}
/**
* CookieListItem as specified by W3C Cookie Store API
*/
interface CookieListItem {
/** A string with the name of a cookie */
name: string;
/** A string containing the value of the cookie */
value: string;
/** A number of milliseconds or Date interface containing the expires of the cookie */
expires?: Date | number;
domain?: string;
path?: string;
sameSite?: 'strict' | 'lax' | 'none';
secure?: boolean;
}
/**
* Map of password IDs to password strings for rotation
*/
type PasswordsMap = Record<string, string>;
/**
* Union type for password specification
*/
type Password = PasswordsMap | string;
/**
* Cookie serialization options (excluding encode)
*/
type CookieOptions = Omit<CookieSerializeOptions, "encode">;/**
* Default session options applied automatically
*/
const defaultOptions = {
ttl: 1209600, // 14 days in seconds (fourteenDaysInSeconds)
cookieOptions: {
httpOnly: true,
secure: true,
sameSite: "lax",
path: "/"
// maxAge is computed dynamically as ttl - 60 (timestampSkewSec)
}
};iron-session performs validation and throws descriptive errors for common issues:
Common Error Patterns:
try {
const session = await getIronSession(req, res, {
password: "short", // Error: Password too short
cookieName: "app-session"
});
} catch (error) {
console.error(error.message); // "iron-session: Bad usage. Password must be at least 32 characters long."
}
// Expired/invalid sessions don't throw - they return empty objects
const session = await getIronSession(req, res, validOptions);
console.log(session.userId); // undefined if session expired/invalidimport { getIronSession } from "iron-session";
app.get('/profile', async (req, res) => {
const session = await getIronSession(req, res, sessionOptions);
res.json({ user: session.user });
});// pages/api/session.js
import { getIronSession } from "iron-session";
export default async function handler(req, res) {
const session = await getIronSession(req, res, sessionOptions);
// Use session...
}// app/api/session/route.ts
import { cookies } from "next/headers";
import { getIronSession } from "iron-session";
export async function POST() {
const session = await getIronSession(cookies(), sessionOptions);
session.user = { id: 1, name: "John" };
await session.save();
return Response.json({ success: true });
}// app/profile/page.tsx
import { cookies } from "next/headers";
import { getIronSession } from "iron-session";
async function ProfilePage() {
const session = await getIronSession(cookies(), sessionOptions);
if (!session.user) {
return <div>Please log in</div>;
}
return <div>Welcome, {session.user.name}</div>;
}// app/login/actions.ts
"use server";
import { cookies } from "next/headers";
import { getIronSession } from "iron-session";
export async function loginAction(formData: FormData) {
const session = await getIronSession(cookies(), sessionOptions);
// Authenticate user...
session.user = { id: userId, name: username };
await session.save();
}// Current configuration
const sessionOptions = {
cookieName: "session",
password: {
1: "old_password_32_characters_minimum",
2: "new_password_32_characters_minimum" // This will be used for new sessions
}
};
// iron-session will:
// - Use password ID 2 for encrypting new sessions
// - Accept both password IDs 1 and 2 for decrypting existing sessions
// - Allow gradual migration as old sessions expire