{"id":13406422,"url":"https://github.com/vvo/iron-session","last_synced_at":"2026-10-19T00:48:33.838Z","repository":{"id":37096891,"uuid":"245132958","full_name":"vvo/iron-session","owner":"vvo","description":"🛠 Secure, stateless, and cookie-based session library for Next.js or any JavaScript framework","archived":false,"fork":false,"pushed_at":"2026-10-12T20:48:31.000Z","size":8621,"stargazers_count":4146,"open_issues_count":1,"forks_count":255,"subscribers_count":9,"default_branch":"main","last_synced_at":"2026-10-14T05:40:51.246Z","etag":null,"topics":["authentication","cookies","expressjs","nextjs","nodejs","serverless","session","stateless"],"latest_commit_sha":null,"homepage":"https://get-iron-session.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vvo.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yaml","license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","claude":null,"gemini":null,"cursor":null,"copilot":null,"dco":null,"cla":null,"disclosure":null},"funding":{"github":["vvo","brc-dd"]}},"created_at":"2020-03-05T10:27:50.000Z","updated_at":"2026-10-12T23:47:33.000Z","dependencies_parsed_at":"2026-09-28T22:55:35.385Z","dependency_job_id":null,"html_url":"https://github.com/vvo/iron-session","commit_stats":{"total_commits":737,"total_committers":49,"mean_commits":"15.040816326530612","dds":0.5101763907734057,"last_synced_commit":"f5ac7e56cfa41cedddacf272614dbd25731a30ac"},"previous_names":["vvo/next-iron-session"],"tags_count":55,"template":false,"template_full_name":null,"purl":"pkg:github/vvo/iron-session","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvo%2Firon-session","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvo%2Firon-session/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvo%2Firon-session/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvo%2Firon-session/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vvo","download_url":"https://codeload.github.com/vvo/iron-session/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vvo%2Firon-session/sbom","scorecard":{"id":1239271,"data":{"date":"2025-10-20","repo":{"name":"github.com/vvo/iron-session","commit":"d28012dba58ea39ebc828240cf0345459e7c9aa9"},"scorecard":{"version":"v5.3.1-0.20251020080748-9e702f4c9dc6","commit":"9e702f4c9dc6992745395f4c8ac89942d0cf87ea"},"score":2.8,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/9e702f4c9dc6992745395f4c8ac89942d0cf87ea/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/9e702f4c9dc6992745395f4c8ac89942d0cf87ea/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/9e702f4c9dc6992745395f4c8ac89942d0cf87ea/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/9e702f4c9dc6992745395f4c8ac89942d0cf87ea/docs/checks.md#binary-artifacts"}},{"name":"Code-Review","score":0,"reason":"Found 1/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/9e702f4c9dc6992745395f4c8ac89942d0cf87ea/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yaml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/9e702f4c9dc6992745395f4c8ac89942d0cf87ea/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yaml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/vvo/iron-session/ci.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yaml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/vvo/iron-session/ci.yaml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yaml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/vvo/iron-session/ci.yaml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yaml:25: update your workflow using https://app.stepsecurity.io/secureworkflow/vvo/iron-session/ci.yaml/main?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/9e702f4c9dc6992745395f4c8ac89942d0cf87ea/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/9e702f4c9dc6992745395f4c8ac89942d0cf87ea/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/9e702f4c9dc6992745395f4c8ac89942d0cf87ea/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/9e702f4c9dc6992745395f4c8ac89942d0cf87ea/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE.md:0","Info: FSF or OSI recognized license: MIT License: LICENSE.md:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/9e702f4c9dc6992745395f4c8ac89942d0cf87ea/docs/checks.md#license"}},{"name":"Branch-Protection","score":-1,"reason":"internal error: error during branchesHandler.setup: internal error: some github tokens can't read classic branch protection rules: https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md","details":null,"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/9e702f4c9dc6992745395f4c8ac89942d0cf87ea/docs/checks.md#branch-protection"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/9e702f4c9dc6992745395f4c8ac89942d0cf87ea/docs/checks.md#signed-releases"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 27 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/9e702f4c9dc6992745395f4c8ac89942d0cf87ea/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":0,"reason":"17 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-mwcw-c2x4-8c55","Warn: Project is vulnerable to: GHSA-3h52-269p-cp9r","Warn: Project is vulnerable to: GHSA-4342-x723-ch2f","Warn: Project is vulnerable to: GHSA-7gfc-8cq8-jh5f","Warn: Project is vulnerable to: GHSA-7m27-7ghc-44w9","Warn: Project is vulnerable to: GHSA-f82v-jwr5-mffw","Warn: Project is vulnerable to: GHSA-fr5h-rqp8-mj6g","Warn: Project is vulnerable to: GHSA-g5qg-72qw-gw5v","Warn: Project is vulnerable to: GHSA-g77x-44xx-532m","Warn: Project is vulnerable to: GHSA-gp8f-8m3g-qvj9","Warn: Project is vulnerable to: GHSA-qpjv-v59x-3qc4","Warn: Project is vulnerable to: GHSA-xv57-4mr9-wg8v"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/9e702f4c9dc6992745395f4c8ac89942d0cf87ea/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-10-28T03:26:54.767Z","repository_id":37096891,"created_at":"2025-10-28T03:26:54.768Z","updated_at":"2025-10-28T03:26:54.768Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":344872684,"owners_count":38344159,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-10-03T21:59:58.778Z","status":"online","status_checked_at":"2026-10-18T02:00:06.448Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","cookies","expressjs","nextjs","nodejs","serverless","session","stateless"],"created_at":"2024-07-30T19:02:29.891Z","updated_at":"2026-10-19T00:48:33.831Z","avatar_url":"https://github.com/vvo.png","language":"TypeScript","funding_links":["https://github.com/sponsors/vvo","https://github.com/sponsors/brc-dd"],"categories":["Miscellaneous","miscellaneous","TypeScript","Auth / Authz","⚙️ Backend \u0026 APIs"],"sub_categories":["Miscellaneous"],"readme":"# iron-session ![GitHub Workflow Status (with event)](https://img.shields.io/github/actions/workflow/status/vvo/iron-session/ci.yaml) [![GitHub license](https://img.shields.io/github/license/vvo/iron-session?style=flat)](https://github.com/vvo/iron-session/blob/master/LICENSE) [![npm](https://img.shields.io/npm/v/iron-session)](https://www.npmjs.com/package/iron-session) ![npm](https://img.shields.io/npm/dm/iron-session) ![npm package minimized gzipped size (select exports)](https://img.shields.io/bundlejs/size/iron-session?exports=getIronSession)\n\n**`iron-session` is a secure, stateless, and cookie-based session library for JavaScript.**\n\n\u003e [!IMPORTANT]\n\u003e **Coming from v8?** Read [Upgrading to v9](#upgrading-to-v9) for the two changes\n\u003e most apps need, or [MIGRATION.md](./MIGRATION.md) for the full guide. v9 needs\n\u003e Node 22.13+ and is ESM-only.\n\n---\n\nThe 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\n[Ruby On Rails](https://guides.rubyonrails.org/security.html#session-storage).\n\n\u003cp align=\"center\"\u003e\u003ci\u003eOnline demo and examples: \u003ca href=\"https://get-iron-session.vercel.app/\"\u003ehttps://get-iron-session.vercel.app\u003c/a\u003e\u003c/i\u003e 👀 \u003cbr/\u003e\n \u003ci\u003eFeatured in the \u003ca href=\"https://nextjs.org/docs/app/guides/authentication\"\u003eNext.js documentation\u003c/a\u003e\u003c/i\u003e ⭐️\u003c/p\u003e\n\n## Table of Contents\n\n- [Table of Contents](#table-of-contents)\n- [Installation](#installation)\n- [Upgrading to v9](#upgrading-to-v9)\n- [Usage](#usage)\n- [Examples](#examples)\n- [Runtimes](#runtimes)\n- [Session size](#session-size)\n- [Watching for unreadable cookies](#watching-for-unreadable-cookies)\n- [Validating session data](#validating-session-data)\n- [Project status](#project-status)\n- [Session options](#session-options)\n- [API](#api)\n  - [`getIronSession\u003cT\u003e(req, res, sessionOptions): Promise\u003cIronSession\u003cT\u003e\u003e`](#getironsessiontreq-res-sessionoptions-promiseironsessiont)\n  - [`getIronSession\u003cT\u003e(cookieStore, sessionOptions): Promise\u003cIronSession\u003cT\u003e\u003e`](#getironsessiontcookiestore-sessionoptions-promiseironsessiont)\n  - [`nodeCookies`, `webCookies`, `nextProxyCookies`](#nodecookiesreq-res-webcookiesrequest-responseorheaders-nextproxycookiesrequest-response)\n  - [`session.save(): Promise\u003cvoid\u003e`](#sessionsave-promisevoid)\n  - [`session.destroy(): void`](#sessiondestroy-void)\n  - [`session.updateConfig(sessionOptions: SessionOptions): void`](#sessionupdateconfigsessionoptions-sessionoptions-void)\n  - [`sealData(data: unknown, { password, ttl }): Promise\u003cstring\u003e`](#sealdatadata-unknown--password-ttl--promisestring)\n  - [`unsealData\u003cT\u003e(seal: string, { password, ttl }): Promise\u003cT\u003e`](#unsealdatatseal-string--password-ttl--promiset)\n- [FAQ](#faq)\n  - [Why use pure cookies for sessions?](#why-use-pure-cookies-for-sessions)\n  - [How to invalidate sessions?](#how-to-invalidate-sessions)\n  - [Can I use something else than cookies?](#can-i-use-something-else-than-cookies)\n  - [How is this different from JWT?](#how-is-this-different-from-jwt)\n- [Credits](#credits)\n- [Good Reads](#good-reads)\n\n## Installation\n\n```sh\npnpm add iron-session\n```\n\nv9 needs **Node 22.13 or later** and is **ESM-only**. `require()` still works on\nNode 22.13+, which supports `require()` of an ES module. If you are stuck on an\nolder Node, stay on v8: `pnpm add iron-session@8`.\n\n## Upgrading to v9\n\nMost apps change two things. Both are things v8 got wrong quietly.\n\n**1. Store timestamps, not `Date` objects.**\n\n```diff\n- session.lastSeen = new Date();\n+ session.lastSeen = Date.now();\n```\n\nv8 turned a `Date` into a string when sealing, so the type you wrote was not the\ntype you read back. v9 throws and names the field.\n\n**2. Handle a session that does not exist yet.**\n\n```diff\n- const userId = session.user.id;\n+ const userId = session.user?.id;\n```\n\nReads are typed as `Partial\u003cT\u003e` now. A first visit, an expired cookie and a\n`destroy()` all leave you an empty object, so the old type let this compile and\nthen throw at runtime.\n\nNothing else is required. `getIronSession(req, res, options)` and\n`getIronSession(await cookies(), options)` both still work, v9 reads v8 cookies\nand v8 reads v9 cookies, so you can roll a deploy back without signing everyone\nout. If you had `as any` on `await cookies()`, delete it.\n\nWorth adopting while you are here:\n\n- [`nextProxyCookies`](#runtimes) if you ever tried to save a session in Next.js\n  middleware and it did not stick.\n- [`onUnsealError`](#watching-for-unreadable-cookies) to see why cookies get\n  rejected instead of guessing.\n- [`chunk: true`](#session-size) if your session outgrew one cookie.\n\nThe full guide, including the removed APIs and the security fix that signs pre-v8\ncookies out once, is in [MIGRATION.md](./MIGRATION.md).\n\n## Usage\n\n_We have extensive examples here too: https://get-iron-session.vercel.app/._\n\nTo get a session, there's a single method to know: `getIronSession`.\n\n```ts\n// Next.js API Routes and Node.js/Express/Connect.\nimport { getIronSession } from \"iron-session\";\n\nexport async function get(req, res) {\n  const session = await getIronSession(req, res, { password: \"...\", cookieName: \"...\" });\n  return session;\n}\n\nexport async function post(req, res) {\n  const session = await getIronSession(req, res, { password: \"...\", cookieName: \"...\" });\n  session.username = \"Alison\";\n  await session.save();\n}\n```\n\n```ts\n// Next.js Route Handlers (App Router)\nimport { cookies } from \"next/headers\";\nimport { getIronSession } from \"iron-session\";\n\nexport async function GET() {\n  const session = await getIronSession(await cookies(), { password: \"...\", cookieName: \"...\" });\n  return session;\n}\n\nexport async function POST() {\n  const session = await getIronSession(await cookies(), { password: \"...\", cookieName: \"...\" });\n  session.username = \"Alison\";\n  await session.save();\n}\n```\n\n```tsx\n// Next.js Server Components and Server Actions (App Router)\nimport { cookies } from \"next/headers\";\nimport { getIronSession } from \"iron-session\";\n\nasync function getIronSessionData() {\n  const session = await getIronSession(await cookies(), { password: \"...\", cookieName: \"...\" });\n  return session;\n}\n\nasync function Profile() {\n  const session = await getIronSessionData();\n\n  return \u003cdiv\u003e{session.username}\u003c/div\u003e;\n}\n```\n\n```ts\n// Next.js proxy.ts (middleware.ts before Next 16)\nimport { NextResponse, type NextRequest } from \"next/server\";\nimport { getIronSession, nextProxyCookies } from \"iron-session\";\n\nexport async function proxy(request: NextRequest) {\n  const response = NextResponse.next();\n  const session = await getIronSession(nextProxyCookies(request, response), options);\n\n  session.lastSeen = Date.now();\n  await session.save();\n\n  return response;\n}\n```\n\nMiddleware needs the adapter because Next only merges a cookie into the current\nrender when it goes through `response.cookies.set()`. Writing a raw `Set-Cookie`\nheader there looks like it works and then has no effect.\n\n## Examples\n\nRunnable examples for every pattern: https://get-iron-session.vercel.app/. Two\nof them are where to start, and they follow the\n[Next.js authentication guide](https://nextjs.org/docs/app/guides/authentication):\n\n- [Server Components and Server Actions](https://get-iron-session.vercel.app/app-router-server-component-and-action)\n  ([source](./examples/next/src/app/app-router-server-component-and-action)).\n  A form posts to a Server Action, the action writes the session, the page reads\n  it on the server. This is the default.\n- [Cache Components and Partial Prerendering](https://get-iron-session.vercel.app/app-router-cache-components)\n  ([source](./examples/next/src/app/app-router-cache-components)), for Next.js\n  16 with `cacheComponents` on. Also covers `useActionState` for form errors and\n  session rotation in `proxy.ts`.\n\nThree rules once `cacheComponents` is on:\n\n- A session read is dynamic, because it reads a cookie. Put it inside a\n  `\u003cSuspense\u003e` boundary and the rest of the page still prerenders.\n- Never read a session inside `use cache`. Runtime APIs are rejected there, and\n  whatever it renders is shared between visitors.\n- `export const dynamic = \"force-dynamic\"` is no longer allowed, and no longer\n  needed. The `\u003cSuspense\u003e` boundary is what marks the dynamic part.\n\nThe session belongs next to the data it protects: read it in the Server\nComponent, Server Action or Route Handler that needs it. A layout does not\nprotect the pages under it, and neither does a redirect in `proxy.ts`.\n\n## Runtimes\n\n`getIronSession(req, res, options)` covers Node, Express, Connect and Next.js\nAPI routes, and `getIronSession(await cookies(), options)` covers the Next.js App\nRouter. When you want to be explicit, or when your framework hands you something\nelse, pass an adapter instead:\n\n| Adapter                                  | For                                                                        |\n| ---------------------------------------- | -------------------------------------------------------------------------- |\n| `nodeCookies(req, res)`                  | Node `http`, Express, Connect, Next.js API routes                          |\n| `webCookies(request, responseOrHeaders)` | Anything web-standard: Hono, Bun, Deno, Cloudflare Workers, Route Handlers |\n| `nextProxyCookies(request, response)`    | Next.js Proxy (middleware), `proxy.ts`                                     |\n\nAnything with `get(name)` and `set(name, value, options)`, like Next's\n`cookies()`, can be passed directly. If your framework has neither, a cookie jar\nis two functions:\n\n```ts\nconst session = await getIronSession(\n  {\n    read: (name) =\u003e myFramework.getCookie(name),\n    write: (name, value, options) =\u003e myFramework.setCookie(name, value, options),\n  },\n  options,\n);\n```\n\n## Session size\n\nA browser refuses a cookie over 4096 bytes, and iron-session throws rather than\nletting one be silently dropped. Encryption adds overhead, so plan for roughly\n3KB of actual data.\n\nIf you need more, `chunk: true` splits the session across several cookies. Before\nyou reach for it, know what the real limit is: every cookie is sent on **every\nrequest**, and proxies cap the whole `Cookie` header well below what a few\nchunks produce. nginx allows 8KB by default and a CDN in front of it may allow\nless. Going over returns a 400 or 431 at the edge, before your code runs.\niron-session refuses more than 4 chunks for that reason.\n\nThe scalable answer is to keep an id in the session and the data in your\ndatabase:\n\n```ts\nsession.userId = user.id; // small, stateless\nconst user = await db.user.findUnique({ where: { id: session.userId } });\n```\n\n## Watching for unreadable cookies\n\nWhen a cookie cannot be read, iron-session starts a new empty session instead of\nthrowing. It has to: it cannot tell a tampered cookie from a password you\nrotated out or a seal that simply expired, and a 500 on every request would be\nworse. That makes real problems invisible, so log them:\n\n```ts\nconst options = {\n  cookieName: \"session\",\n  password: process.env.SESSION_PASSWORD,\n  onUnsealError: (reason, error) =\u003e {\n    // \"expired\" is normal, that is how sessions end.\n    if (reason !== \"expired\") {\n      logger.warn({ reason, error }, \"session cookie rejected\");\n    }\n  },\n};\n```\n\nA burst of `\"unknown-password\"` usually means a password rotation went wrong. A\nburst of `\"invalid\"` can mean someone is probing your cookies.\n\n## Validating session data\n\nThere is no `validate` option, on purpose. If you change the shape of your\nsession, old cookies still decrypt into the old shape, and the place to handle\nthat is the wrapper you already have:\n\n```ts\n// lib/session.ts\nexport async function getSession() {\n  const session = await getIronSession\u003cSession\u003e(await cookies(), options);\n\n  if (session.user \u0026\u0026 !SessionSchema.safeParse({ ...session }).success) {\n    session.destroy();\n  }\n\n  return session;\n}\n```\n\n## Project status\n\n✅ Production ready and maintained.\n\n## Session options\n\nTwo options are required: `password` and `cookieName`. Everything else is automatically computed and usually doesn't need to be changed.\n\n- `password`, **required**: Private key used to encrypt the cookie. It has to be at least 32 characters long. Use \u003chttps://1password.com/password-generator/\u003e 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 key for new cookies.\n- `cookieName`, **required**: Name of the cookie to be stored\n- `ttl`, _optional_: In seconds. Default to the equivalent of 14 days. Setting it to `0` means the seal never expires, which also means it can never be revoked: do not use `0` for authentication.\n- `chunk`, _optional_: Split a session that does not fit in one cookie across several cookies. Defaults to `false`. See [Session size](#session-size) before turning it on.\n- `onUnsealError`, _optional_: Called when an existing cookie could not be read, with a reason of `\"expired\"`, `\"invalid\"` or `\"unknown-password\"`. The session is reset to empty either way, so this is for logging. See [Watching for unreadable cookies](#watching-for-unreadable-cookies).\n- `cookieOptions`, _optional_: Any [Set-Cookie attribute](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#attributes) supported by [jshttp/cookie](https://github.com/jshttp/cookie). Default to:\n\n  ```js\n  {\n    httpOnly: true,\n    secure: true, // set this to false in local (non-HTTPS) development\n    sameSite: \"lax\",// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#lax\n    maxAge: (ttl === 0 ? 2147483647 : ttl) - 60, // Expire cookie before the session expires. A ttl of 60 or less keeps its full value.\n    path: \"/\",\n  }\n  ```\n\n## API\n\n### `getIronSession\u003cT\u003e(req, res, sessionOptions): Promise\u003cIronSession\u003cT\u003e\u003e`\n\n```ts\ntype SessionData = {\n  // Your data\n};\n\nconst session = await getIronSession\u003cSessionData\u003e(req, res, sessionOptions);\n```\n\n### `getIronSession\u003cT\u003e(cookieStore, sessionOptions): Promise\u003cIronSession\u003cT\u003e\u003e`\n\n```ts\ntype SessionData = {\n  // Your data\n};\n\nconst session = await getIronSession\u003cSessionData\u003e(await cookies(), sessionOptions);\n```\n\nReads are typed as `Partial\u003cT\u003e`, because a session that does not exist yet is an\nempty object. Use optional chaining, or narrow once and pass the result around.\n\n### `nodeCookies(req, res)`, `webCookies(request, responseOrHeaders)`, `nextProxyCookies(request, response)`\n\nCookie jars you pass in place of `req, res`, for when the shorthand cannot tell\nwhat your framework wants. See [Runtimes](#runtimes).\n\n```ts\nimport { getIronSession, nextProxyCookies } from \"iron-session\";\n\nconst session = await getIronSession(nextProxyCookies(request, response), sessionOptions);\n```\n\n### `session.save(): Promise\u003cvoid\u003e`\n\nSaves the session. This is an asynchronous operation. It must be done and awaited before headers are sent to the client.\n\n```ts\nawait session.save();\n```\n\n### `session.destroy(): void`\n\nDestroys the session. This is a synchronous operation as it only removes the cookie. It must be done before headers are sent to the client.\n\n```ts\nsession.destroy();\n```\n\n`destroy()` is terminal. A `save()` after it is ignored, so a logout handler that calls both still signs the user out. Writing fields back into the session and then saving throws, because the last `Set-Cookie` would win and leave the user signed in.\n\n### `session.updateConfig(sessionOptions: SessionOptions): void`\n\nUpdates the configuration of the session with new session options. You still need to call save() if you want them to be applied.\n\nIt rebuilds the whole configuration, including the password, so this is what you use to rotate a password mid-request. In v8 a new password passed here was ignored.\n\n### `sealData(data: unknown, { password, ttl }): Promise\u003cstring\u003e`\n\nThis is the underlying method and seal mechanism that powers `iron-session`. You can use it to seal any `data` you want and pass it around. One usecase are magic links: you generate a seal that contains a user id to login and send it to a route on your website (like `/magic-login`). Once received, you can safely decode the seal with `unsealData` and log the user in.\n\n### `unsealData\u003cT\u003e(seal: string, { password, ttl }): Promise\u003cT\u003e`\n\nThis is the opposite of `sealData` and allow you to decode a seal to get the original data back.\n\n## FAQ\n\n### Why use pure cookies for sessions?\n\nThis makes your sessions stateless: since the data is passed around in cookies, you do not need any server or service to store session data.\n\nMore information can also be found on the [Ruby On Rails website](https://guides.rubyonrails.org/security.html#session-storage) which uses the same technique.\n\n### How to invalidate sessions?\n\nSessions cannot be instantly invalidated (or \"disconnect this customer\") as there is typically no state stored about sessions on the server by default. However, in most applications, the first step upon receiving an authenticated request is to validate the user and their permissions in the database. So, to easily disconnect customers (or invalidate sessions), you can add an `isBlocked`` state in the database and create a UI to block customers.\n\nThen, every time a request is received that involves reading or altering sensitive data, make sure to check this flag.\n\n### Can I use something else than cookies?\n\nYes, we expose `sealData` and `unsealData` which are not tied to cookies. This way you can seal and unseal any object in your application and move seals around to login users.\n\n### How is this different from [JWT](https://jwt.io/)?\n\nNot so much:\n\n- JWT is a standard, it stores metadata in the JWT token themselves to ensure communication between different systems is flawless.\n- JWT tokens are not encrypted, the payload is visible by customers if they manage to inspect the seal. You would have to use [JWE](https://tools.ietf.org/html/rfc7516) to achieve the same.\n- @hapi/iron mechanism is not a standard, it's a way to sign and encrypt data into seals\n\nDepending on your own needs and preferences, `iron-session` may or may not fit you.\n\n## Used by\n\n[![Used by](https://api.usedby.dev/npm/iron-session?max=50\u0026sort=stars)](https://github.com/vvo/iron-session/network/dependents)\n\nGenerated with [usedby.dev](https://usedby.dev/)\n\n## Credits\n\n- [Eran Hammer and hapi.js contributors](https://github.com/hapijs/iron/graphs/contributors)\n  for creating the underlying cryptography library\n  [`@hapi/iron`](https://hapi.dev/module/iron/).\n- [Divyansh Singh](https://github.com/brc-dd) for reimplementing `@hapi/iron` as\n  [`iron-webcrypto`](https://github.com/brc-dd/iron-webcrypto) using standard\n  web APIs.\n- [Hoang Vo](https://github.com/hoangvvo) for advice and guidance while building\n  this module. Hoang built\n  [`next-connect`](https://github.com/hoangvvo/next-connect) and\n  [`next-session`](https://github.com/hoangvvo/next-session).\n- All the\n  [contributors](https://github.com/vvo/iron-session/graphs/contributors) for\n  making this project better.\n\n## Good Reads\n\n- \u003chttps://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html\u003e\n- \u003chttps://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvvo%2Firon-session","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvvo%2Firon-session","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvvo%2Firon-session/lists"}