Baike.dev
All toolsAI codingTrendingOpen sourceNewsSubmit
Log in
< Back to tools
I

iron-session

> 前端框架
Open source

Secure, stateless, and cookie-based session library for Next.js or any JavaScript framework

4.1K stars0 likes0 views
WebsiteGitHub

About

Secure, stateless, and cookie-based session library for Next.js or any JavaScript framework

# iron-session [](https://github.com/vvo/iron-session/blob/master/LICENSE) [](https://www.npmjs.com/package/iron-session) **`iron-session` is a secure, stateless, and cookie-based session library for JavaScript.** > [!IMPORTANT] > **Coming from v8?** Read [Upgrading to v9](#upgrading-to-v9) for the two changes > most apps need, or [MIGRATION.md](./MIGRATION.md) for the full guide. v9 needs > Node 22.13+ and is ESM-only. --- The session data is stored in signed and encrypted cookies which are decoded by your server code in a stateless fashion (= no network involved). This is the same technique used by frameworks like [Ruby On Rails](https://guides.rubyonrails.org/security.html#session-storage).

Online demo and examples: https://get-iron-session.vercel.app
Featured in the Next.js documentation ⭐️

## Table of Contents - [Table of Contents](#table-of-contents) - [Installation](#installation) - [Upgrading to v9](#upgrading-to-v9) - [Usage](#usage) - [Examples](#examples) - [Runtimes](#runtimes) - [Session size](#session-size) - [Watching for unreadable cookies](#watching-for-unreadable-cookies) - [Validating session data](#validating-session-data) - [Project status](#project-status) - [Session options](#session-options) - [API](#api) - [`getIronSession(req, res, sessionOptions): Promise>`](#getironsessiontreq-res-sessionoptions-promiseironsessiont) - [`getIronSession(cookieStore, sessionOptions): Promise>`](#getironsessiontcookiestore-sessionoptions-promiseironsessiont) - [`nodeCookies`, `webCookies`, `nextProxyCookies`](#nodecookiesreq-res-webcookiesrequest-responseorheaders-nextproxycookiesrequest-response) - [`session.save(): Promise`](#sessionsave-promisevoid) - [`session.destroy(): void`](#sessiondestroy-void) - [`session.updateConfig(sessionOptions: SessionOptions): void`](#sessionupdateconfigsessionoptions-sessionoptions-void) - [`sealData(data: unknown, { password, ttl }): Promise`](#sealdatadata-unknown--password-ttl--promisestring) - [`unsealData(seal: string, { password, ttl }): Promise`](#unsealdatatseal-string--password-ttl--promiset) - [FAQ](#faq) - [Why use pure cookies for sessions?](#why-use-pure-cookies-for-sessions) - [How to invalidate sessions?](#how-to-invalidate-sessions) - [Can I use something else than cookies?](#can-i-use-something-else-than-cookies) - [How is this different from JWT?](#how-is-this-different-from-jwt) - [Credits](#credits) - [Good Reads](#good-reads) ## Installation ```sh pnpm add iron-session ``` v9 needs **Node 22.13 or later** and is **ESM-only**. `require()` still works on Node 22.13+, which supports `require()` of an ES module. If you are stuck on an older Node, stay on v8: `pnpm add iron-session@8`. ## Upgrading to v9 Most apps change two things. Both are things v8 got wrong quietly. **1. Store timestamps, not `Date` objects.** ```diff - session.lastSeen = new Date(); + session.lastSeen = Date.now(); ``` v8 turned a `Date` into a string when sealing, so the type you wrote was not the type you read back. v9 throws and names the field. **2. Handle a session that does not exist yet.** ```diff - const userId = session.user.id; + const userId = session.user?.id; ``` Reads are typed as `Partial` now. A first visit, an expired cookie and a `destroy()` all leave you an empty object, so the old type let this compile and then throw at runtime. Nothing else is required. `getIronSession(req, res, options)` and `getIronSession(await cookies(), options)` both still work, v9 reads v8 cookies and v8 reads v9 cookies, so you can roll a deploy back without signing everyone out. If you had `as any` on `await cookies()`, delete it. Worth adopting while you are here: - [`nextProxyCookies`](#runtimes) if you ever tried to save a session in Next.js middleware and it did not stick. - [`onUnsealError`](#watching-for-unreadable-cookies) to see why cookies get rejected instead of guessing. - [`chunk: true`](#session-size) if your session outgrew one cookie. The full guide, including the removed APIs and the security fix that signs pre-v8 cookies out once, is in [MIGRATION.md](./MIGRATION.md). ## Usage _We have extensive examples here too: https://get-iron-session.vercel.app/._ To get a session, there's a single method to know: `getIronSession`. ```ts // Next.js API Routes and Node.js/Express/Connect. import { getIronSession } from "iron-session"; export async function get(req, res) { const session = await getIronSession(req, res, { password: "...", cookieName: "..." }); return session; } export async function post(req, res) { const session = await getIronSession(req, res, { password: "...", cookieName: "..." }); session.username = "Alison"; await session.save(); } ``` ```ts // Next.js Route Handlers (App Router) import { cookies } from "next/headers"; import { getIronSession } from "iron-session"; export async function GET() { const session = await getIronSession(await cookies(), { password: "...", cookieName: "..." }); return session; } export async function POST() { const session = await getIronSession(await cookies(), { password: "...", cookieName: "..." }); session.username = "Alison"; await session.save(); } ``` ```tsx // Next.js Server Components and Server Actions (App Router) import { cookies } from "next/headers"; import { getIronSession } from "iron-session"; async function getIronSessionData() { const session = await getIronSession(await cookies(), { password: "...", cookieName: "..." }); return session; } async function Profile() { const session = await getIronSessionData(); return ; } ``` ```ts // Next.js proxy.ts (middleware.ts before Next 16) import { NextResponse, type NextRequest } from "next/server"; import { getIronSession, nextProxyCookies } from "iron-session"; export async function proxy(request: NextRequest) { const response = NextResponse.next(); const session = await getIronSession(nextProxyCookies(request, response), options); session.lastSeen = Date.now(); await session.save(); return response; } ``` Middleware needs the adapter because Next only merges a cookie into the current render when it goes through `response.cookies.set()`. Writing a raw `Set-Cookie` header there looks like it works and then has no effect. ## Examples Runnable examples for every pattern: https://get-iron-session.vercel.app/. Two of them are where to start, and they follow the [Next.js authentication guide](https://nextjs.org/docs/app/guides/authentication): - [Server Components and Server Actions](https://get-iron-session.vercel.app/app-router-server-component-and-action) ([source](./examples/next/src/app/app-router-server-component-and-action)). A form posts to a Server Action, the action writes the session, the page reads it on the server. This is the default. - [Cache Components and Partial Prerendering](https://get-iron-session.vercel.app/app-router-cache-components) ([source](./examples/next/src/app/app-router-cache-components)), for Next.js 16 with `cacheComponents` on. Also covers `useActionState` for form errors and session rotation in `proxy.ts`. Three rules once `cacheComponents` is on: - A session read is dynamic, because it reads a cookie. Put it inside a `` boundary and the rest of the page still prerenders. - Never read a session inside `use cache`. Runtime APIs are rejected there, and whatever it renders is shared between visitors. - `export const dynamic = "force-dynamic"` is no longer allowed, and no longer needed. The `` boundary is what marks the dynamic part. The session belongs next to the data it protects: read it in the Server Component, Server Action or Route Handler that needs it. A layout does not protect the pages under it, and neither does a redirect in `proxy.ts`. ## Runtimes `getIronSession(req, res, options)` covers Node, Express, Connect and Next.js API routes, and `getIronSession(await cookies(), options)` covers the Next.js App Router. When you want to be explicit, or when your framework hands you something else, pass an adapter instead: | Adapter | For | | ---------------------------------------- | -------------------------------------------------------------------------- | | `nodeCookies(req, res)` | Node `http`, Express, Connect, Next.js API routes | | `webCookies(request, responseOrHeaders)` | Anything web-standard: Hono, Bun, Deno, Cloudflare Workers, Route Handlers | | `nextProxyCookies(request, response)` | Next.js Proxy (middleware), `proxy.ts` | Anything with `get(name)` and `set(name, value, options)`, like Next's `cookies()`, can be passed directly. If your framework has neither, a cookie jar is two functions: ```ts const session = await getIronSession( { read: (name) => myFramework.getCookie(name), write: (name, value, options) => myFramework.setCookie(name, value, options), }, options, ); ``` ## Session size A browser refuses a cookie over 4096 bytes, and iron-session throws rather than letting one be silently dropped. Encryption adds overhead, so plan for roughly 3KB of actual data. If you need more, `chunk: true` splits the session across several cookies. Before you reach for it, know what the real limit is: every cookie is sent on **every request**, and proxies cap the whole `Cookie` header well below what a few chunks produce. nginx allows 8KB by default and a CDN in front of it may allow less. Going over returns a 400 or 431 at the edge, before your code runs. iron-session refuses more than 4 chunks for that reason. The scalable answer is to keep an id in the session and the data in your database: ```ts session.userId = user.id; // small, stateless const user = await db.user.findUnique({ where: { id: session.userId } }); ``` ## Watching for unreadable cookies When a cookie cannot be read, iron-session starts a new empty session instead of throwing. It has to: it cannot tell a tampered cookie from a password you rotated out or a seal that simply expired, and a 500 on every request would be worse. That makes real problems invisible, so log them: ```ts const options = { cookieName: "session", password: process.env.SESSION_PASSWORD, onUnsealError: (reason, error) => { // "expired" is normal, that is how sessions end. if (reason !== "expired") { logger.warn({ reason, error }, "session cookie rejected"); } }, }; ``` A burst of `"unknown-password"` usually means a password rotation went wrong. A burst of `"invalid"` can mean someone is probing your cookies. ## Validating session data There is no `validate` option, on purpose. If you change the shape of your session, old cookies still decrypt into the old shape, and the place to handle that is the wrapper you already have: ```ts // lib/session.ts export async function getSession() { const session = await getIronSession(await cookies(), options); if (session.user && !SessionSchema.safeParse({ ...session }).success) { session.destroy(); } return session; } ``` ## Project status ✅ Production ready and maintained. ## Session options 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 to generate strong passwords. `password` can be either a `string` or an `object` with incrementing keys like this: `{2: "...", 1: "..."}` to allow for password rotation. iron-session will use the highest numbered

Issues· 0 open

View all issuesOpen on GitHub

No open issues yet, or sync has not completed.

> Tags

TypeScriptauthenticationcookiesexpressjsnextjs

No comments yet. Be the first to share.

> Details

PublishedAug 1, 2026
UpdatedSep 17, 2026
Category前端框架
PricingOpen source

> Related tools

R
React
用于构建用户界面的 JavaScript 库
V
Vue.js
渐进式 JavaScript 框架
N
Next.js
基于 React 的全栈 Web 框架