Gathering detailed insights and metrics for @distributed/iron-session
Gathering detailed insights and metrics for @distributed/iron-session
Gathering detailed insights and metrics for @distributed/iron-session
Gathering detailed insights and metrics for @distributed/iron-session
🛠 Secure, stateless, and cookie-based session library for JavaScript
npm install @distributed/iron-session
Typescript
Module System
Min. Node Version
Node Version
NPM Version
TypeScript (100%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
3,977 Stars
809 Commits
260 Forks
12 Watchers
15 Branches
45 Contributors
Updated on Jul 12, 2025
Latest Version
6.3.3
Package Id
@distributed/iron-session@6.3.3
Unpacked Size
251.32 kB
Size
34.31 kB
File Count
59
NPM Version
8.19.3
Node Version
16.19.0
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
7
⭐️ Featured in the Next.js documentation
🛠 Node.js stateless session utility using signed and encrypted cookies to store data. Works with Next.js, Express, and Node.js HTTP servers.
The session data is stored in encrypted cookies ("seals"). And only your server can decode the session data. There are no session ids, making iron 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 (their default strategy).
The underlying cryptography library is iron which was created by the lead developer of OAuth 2.0.
Online demo: https://iron-session-example.vercel.app 👀
Table of contents:
1npm install iron-session 2yarn add iron-session
You can find full featured examples (Next.js, Express) in the examples folder.
The password is a private key you must pass at runtime and builtime (for getServerSideProps), it has to be at least 32 characters long. You can use https://1password.com/password-generator/ to generate strong passwords.
Session duration is 14 days by default, check the API docs for more info.
⚠️ Always store passwords in encrypted environment variables on your platform. Vercel does this automatically.
Login API Route:
1// pages/api/login.ts 2 3import { withIronSessionApiRoute } from "iron-session/next"; 4 5export default withIronSessionApiRoute( 6 async function loginRoute(req, res) { 7 // get user from database then: 8 req.session.user = { 9 id: 230, 10 admin: true, 11 }; 12 await req.session.save(); 13 res.send({ ok: true }); 14 }, 15 { 16 cookieName: "myapp_cookiename", 17 password: "complex_password_at_least_32_characters_long", 18 // secure: true should be used in production (HTTPS) but can't be used in development (HTTP) 19 cookieOptions: { 20 secure: process.env.NODE_ENV === "production", 21 }, 22 }, 23);
User API Route:
1// pages/api/user.ts 2 3import { withIronSessionApiRoute } from "iron-session/next"; 4 5export default withIronSessionApiRoute( 6 function userRoute(req, res) { 7 res.send({ user: req.session.user }); 8 }, 9 { 10 cookieName: "myapp_cookiename", 11 password: "complex_password_at_least_32_characters_long", 12 // secure: true should be used in production (HTTPS) but can't be used in development (HTTP) 13 cookieOptions: { 14 secure: process.env.NODE_ENV === "production", 15 }, 16 }, 17);
Logout Route:
1// pages/api/logout.ts 2 3import { withIronSessionApiRoute } from "iron-session/next"; 4 5export default withIronSessionApiRoute( 6 function logoutRoute(req, res, session) { 7 req.session.destroy(); 8 res.send({ ok: true }); 9 }, 10 { 11 cookieName: "myapp_cookiename", 12 password: "complex_password_at_least_32_characters_long", 13 // secure: true should be used in production (HTTPS) but can't be used in development (HTTP) 14 cookieOptions: { 15 secure: process.env.NODE_ENV === "production", 16 }, 17 }, 18);
getServerSideProps:
1// pages/admin.tsx 2 3import { withIronSessionSsr } from "iron-session/next"; 4 5export const getServerSideProps = withIronSessionSsr( 6 async function getServerSideProps({ req }) { 7 const user = req.session.user; 8 9 if (user.admin !== true) { 10 return { 11 notFound: true, 12 }; 13 } 14 15 return { 16 props: { 17 user: req.session.user, 18 }, 19 }; 20 }, 21 { 22 cookieName: "myapp_cookiename", 23 password: "complex_password_at_least_32_characters_long", 24 // secure: true should be used in production (HTTPS) but can't be used in development (HTTP) 25 cookieOptions: { 26 secure: process.env.NODE_ENV === "production", 27 }, 28 }, 29);
Note: We encourage you to create a withSession
utility so you do not have to repeat the password and cookie name in every route. You can see how to do that in the example.
As of version 6.2.0, this library is compatible with Next.js middlewares locally and when deployed on Vercel.
Since there's no pre-available res
object in Next.js's middlewares, you need to use iron-session this way:
1// /middleware.ts
2import { NextResponse } from "next/server";
3import type { NextRequest } from "next/server";
4import { getIronSession } from "iron-session/edge";
5
6export const middleware = async (req: NextRequest) => {
7 const res = NextResponse.next();
8 const session = await getIronSession(req, res, {
9 cookieName: "myapp_cookiename",
10 password: "complex_password_at_least_32_characters_long",
11 // secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
12 cookieOptions: {
13 secure: process.env.NODE_ENV === "production",
14 },
15 });
16
17 // do anything with session here:
18 const { user } = session;
19
20 // like mutate user:
21 // user.something = someOtherThing;
22 // or:
23 // session.user = someoneElse;
24
25 // uncomment next line to commit changes:
26 // await session.save();
27 // or maybe you want to destroy session:
28 // await session.destroy();
29
30 console.log("from middleware", { user });
31
32 // demo:
33 if (user?.admin !== "true") {
34 // unauthorized to see pages inside admin/
35 return NextResponse.redirect(new URL('/unauthorized', req.url)) // redirect to /unauthorized page
36 }
37
38 return res;
39};
40
41export const config = {
42 matcher: "/admin",
43};
Note: There's a good probability that you can also use iron-session in the context of Cloudflare Workers, try it and let us know.
Huge thanks to Divyansh Singh who ported hapijs/iron to the Web Crypto API and implemented the required changes in iron-session.
Here's an example Login API route that is easier to read because of less nesting:
1// pages/api/login.ts 2 3import { withIronSessionApiRoute } from "iron-session/next"; 4import { ironOptions } from "lib/config"; 5 6export default withIronSessionApiRoute(loginRoute, ironOptions); 7 8async function loginRoute(req, res) { 9 // get user from database then: 10 req.session.user = { 11 id: 230, 12 admin: true, 13 }; 14 await req.session.save(); 15 res.send({ ok: true }); 16}
1// lib/config.ts 2 3export const ironOptions = { 4 cookieName: "myapp_cookiename", 5 password: "complex_password_at_least_32_characters_long", 6 // secure: true should be used in production (HTTPS) but can't be used in development (HTTP) 7 cookieOptions: { 8 secure: process.env.NODE_ENV === "production", 9 }, 10};
If you do not want to pass down the password and cookie name in every API route file or page then you can create wrappers like this:
JavaScript:
1// lib/withSession.js
2
3import { withIronSessionApiRoute, withIronSessionSsr } from "iron-session/next";
4
5const sessionOptions = {
6 password: "complex_password_at_least_32_characters_long",
7 cookieName: "myapp_cookiename",
8 // secure: true should be used in production (HTTPS) but can't be used in development (HTTP)
9 cookieOptions: {
10 secure: process.env.NODE_ENV === "production",
11 },
12};
13
14export function withSessionRoute(handler) {
15 return withIronSessionApiRoute(handler, sessionOptions);
16}
17
18export function withSessionSsr(handler) {
19 return withIronSessionSsr(handler, sessionOptions);
20}
TypeScript:
1// lib/withSession.ts 2 3import { withIronSessionApiRoute, withIronSessionSsr } from "iron-session/next"; 4import { 5 GetServerSidePropsContext, 6 GetServerSidePropsResult, 7 NextApiHandler, 8} from "next"; 9 10const sessionOptions = { 11 password: "complex_password_at_least_32_characters_long", 12 cookieName: "myapp_cookiename", 13 // secure: true should be used in production (HTTPS) but can't be used in development (HTTP) 14 cookieOptions: { 15 secure: process.env.NODE_ENV === "production", 16 }, 17}; 18 19export function withSessionRoute(handler: NextApiHandler) { 20 return withIronSessionApiRoute(handler, sessionOptions); 21} 22 23// Theses types are compatible with InferGetStaticPropsType https://nextjs.org/docs/basic-features/data-fetching#typescript-use-getstaticprops 24export function withSessionSsr< 25 P extends { [key: string]: unknown } = { [key: string]: unknown }, 26>( 27 handler: ( 28 context: GetServerSidePropsContext, 29 ) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>, 30) { 31 return withIronSessionSsr(handler, sessionOptions); 32}
Usage in API Routes:
1// pages/api/login.ts 2 3import { withSessionRoute } from "lib/withSession"; 4 5export default withSessionRoute(loginRoute); 6 7async function loginRoute(req, res) { 8 // get user from database then: 9 req.session.user = { 10 id: 230, 11 admin: true, 12 }; 13 await req.session.save(); 14 res.send("Logged in"); 15}
Usage in getServerSideProps:
1// pages/admin.tsx 2 3import { withSessionSsr } from "lib/withSession"; 4 5export const getServerSideProps = withSessionSsr( 6 async function getServerSideProps({ req }) { 7 const user = req.session.user; 8 9 if (user.admin !== true) { 10 return { 11 notFound: true, 12 }; 13 } 14 15 return { 16 props: { 17 user: req.session.user, 18 }, 19 }; 20 }, 21);
req.session
is automatically populated with the right types so .save() and .destroy() can be called on it.
But you might want to go further and type your session data also. So you can get autocompletion on session.user
for example. To do so, use module augmentation:
1// You may need the next line in some situations 2// import * as IronSession from "iron-session"; 3 4declare module "iron-session" { 5 interface IronSessionData { 6 user?: { 7 id: number; 8 admin?: boolean; 9 }; 10 } 11}
You can put this code anywhere in your project, as long as it is in a file that will be required at some point. For example it could be inside your lib/withSession.ts
wrapper or inside an additional.d.ts
if you're using Next.js.
We've taken this technique from express-session types. If you have any comment on
See examples/express for an example of how to use this with Express.
See examples/koa for an example of how to use this with Koa.
When you want to:
Then you can use multiple passwords:
Week 1:
1withIronSessionApiRoute(handler, { 2 password: { 3 1: "complex_password_at_least_32_characters_long", 4 }, 5});
Week 2:
1withIronSessionApiRoute(handler, { 2 password: { 3 2: "another_password_at_least_32_characters_long", 4 1: "complex_password_at_least_32_characters_long", 5 }, 6});
Notes:
seal
) is always the highest number found in the map (2 in the example).Because of the stateless nature of iron-session
, it's very easy to implement patterns like magic links. For example, you might want to send an email to the user with a link to a page where they will be automatically logged in. Or you might want to send a Slack message to someone with a link to your application where they will be automatically logged in.
Here's how to implement that:
Send an email with a magic link to the user:
1// pages/api/sendEmail.ts
2
3import { sealData } from "iron-session";
4
5export default async function sendEmailRoute(req, res) {
6 const user = getUserFromDatabase(req.query.userId);
7
8 const seal = await sealData(
9 {
10 userId: user.id,
11 },
12 {
13 password: "complex_password_at_least_32_characters_long",
14 },
15 );
16
17 await sendEmail(
18 user.email,
19 "Magic link",
20 `Hey there ${user.name}, <a href="https://myapp.com/api/magicLogin?seal=${seal}">click here to login</a>.`,
21 );
22
23 res.send({ ok: true });
24}
The default ttl
for such seals is 14 days. To specify a ttl
, provide it in seconds like so:
1const fifteenMinutesInSeconds = 15 * 60;
2
3const seal = await sealData(
4 {
5 userId: user.id,
6 },
7 {
8 password: "complex_password_at_least_32_characters_long",
9 ttl: fifteenMinutesInSeconds,
10 },
11);
Login the user automatically and redirect:
1// pages/api/magicLogin.ts
2
3import { unsealData } from "iron-session";
4import { withIronSessionApiRoute } from "iron-session/next";
5
6export default withIronSessionApiRoute(magicLoginRoute, {
7 cookieName: "myapp_cookiename",
8 password: "complex_password_at_least_32_characters_long",
9 cookieOptions: {
10 secure: process.env.NODE_ENV === "production",
11 },
12});
13
14async function magicLoginRoute(req, res) {
15 const { userId } = await unsealData(req.query.seal, {
16 password: "complex_password_at_least_32_characters_long",
17 });
18
19 const user = getUserFromDatabase(userId);
20
21 req.session.user = {
22 id: user.id,
23 };
24
25 await req.session.save();
26
27 res.redirect(`/dashboard`);
28}
You might want to include error handling in the API routes. For example checking if req.session.user
is already defined in login or handling bad seals.
You may want to impersonate your own users, to check how they see your application. This can be extremely useful. For example you could have a page that list all your users and with links you can click to impersonate them.
Login as someone else:
1// pages/api/impersonate.ts 2 3import { withIronSessionApiRoute } from "iron-session/next"; 4 5export default withIronSessionApiRoute(impersonateRoute, { 6 cookieName: "myapp_cookiename", 7 password: "complex_password_at_least_32_characters_long", 8 cookieOptions: { 9 secure: process.env.NODE_ENV === "production", 10 }, 11}); 12 13async function impersonateRoute(req, res) { 14 if (!req.session.isAdmin) { 15 // let's pretend this route does not exists if user is not an admin 16 return res.status(404).end(); 17 } 18 19 req.session.originalUser = req.session.originalUser || req.session.user; 20 req.session.user = { 21 id: req.query.userId, 22 }; 23 await req.session.save(); 24 res.redirect("/dashboard"); 25}
Stop impersonation:
1// pages/api/stopImpersonate.ts
2
3import { withIronSessionApiRoute } from "iron-session/next";
4
5export default withIronSessionApiRoute(stopImpersonateRoute, {
6 cookieName: "myapp_cookiename",
7 password: "complex_password_at_least_32_characters_long",
8 cookieOptions: {
9 secure: process.env.NODE_ENV === "production",
10 },
11});
12
13async function stopImpersonateRoute(req, res) {
14 if (!req.session.isAdmin) {
15 // let's pretend this route does not exists if user is not an admin
16 return res.status(404).end();
17 }
18
19 req.session.user = req.session.originalUser;
20 delete req.session.originalUser;
21 await req.session.save();
22 res.redirect("/dashboard");
23}
If you want cookies to expire when the user closes the browser, pass maxAge: undefined
in cookie options, this way:
1// pages/api/user.ts 2 3import { withIronSessionApiRoute } from "iron-session/next"; 4 5export default withIronSessionApiRoute( 6 function userRoute(req, res) { 7 res.send({ user: req.session.user }); 8 }, 9 { 10 cookieName: "myapp_cookiename", 11 password: "complex_password_at_least_32_characters_long", 12 cookieOptions: { 13 maxAge: undefined, 14 secure: process.env.NODE_ENV === "production", 15 }, 16 }, 17);
Beware, modern browsers might not delete cookies at all using this technique because of session restoring.
This library can be used with Firebase, as long as you set the cookie name to __session
which seems to be the only valid cookie name there.
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. Default to:1{ 2 httpOnly: true, 3 secure: true, // true when using https, false otherwise 4 sameSite: "lax", // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite#lax 5 // The next line makes sure browser will expire cookies before seals are considered expired by the server. It also allows for clock difference of 60 seconds maximum between servers and clients. 6 maxAge: (ttl === 0 ? 2147483647 : ttl) - 60, 7 path: "/", 8 // other options: 9 // domain, if you want the cookie to be valid for the whole domain and subdomains, use domain: example.com 10 // encode, there should be no need to use this option, encoding is done by iron-session already 11 // expires, there should be no need to use this option, maxAge takes precedence 12}
Wraps a Next.js API Route and adds a session
object to the request.
1import { withIronSessionApiRoute } from "iron-session/next"; 2 3export default withIronSessionApiRoute( 4 function userRoute(req, res) { 5 res.send({ user: req.session.user }); 6 }, 7 { 8 cookieName: "myapp_cookiename", 9 password: "complex_password_at_least_32_characters_long", 10 // secure: true should be used in production (HTTPS) but can't be used in development (HTTP) 11 cookieOptions: { 12 secure: process.env.NODE_ENV === "production", 13 }, 14 }, 15); 16 17// You can also pass an async or sync function which takes request and response object and return IronSessionOptions 18export default withIronSessionApiRoute( 19 function userRoute(req, res) { 20 res.send({ user: req.session.user }); 21 }, 22 (req, res) => { 23 // Infer max cookie from request 24 const maxCookieAge = getMaxCookieAge(req); 25 return { 26 cookieName: "myapp_cookiename", 27 password: "complex_password_at_least_32_characters_long", 28 // secure: true should be used in production (HTTPS) but can't be used in development (HTTP) 29 cookieOptions: { 30 // setMaxCookie age here. 31 maxCookieAge, 32 secure: process.env.NODE_ENV === "production", 33 }, 34 }; 35 }, 36);
Wraps a Next.js getServerSideProps and adds a session
object to the request of the context.
1import { withIronSessionSsr } from "iron-session/next"; 2 3export const getServerSideProps = withIronSessionSsr( 4 async function getServerSideProps({ req }) { 5 return { 6 props: { 7 user: req.session.user, 8 }, 9 }; 10 }, 11 { 12 cookieName: "myapp_cookiename", 13 password: "complex_password_at_least_32_characters_long", 14 // secure: true should be used in production (HTTPS) but can't be used in development (HTTP) 15 cookieOptions: { 16 secure: process.env.NODE_ENV === "production", 17 }, 18 }, 19); 20 21// You can also pass an async or sync function which takes request and response object and return IronSessionOptions 22export const getServerSideProps = withIronSessionSsr( 23 async function getServerSideProps({ req }) { 24 return { 25 props: { 26 user: req.session.user, 27 }, 28 }; 29 }, 30 (req, res) => { 31 return { 32 cookieName: "myapp_cookiename", 33 password: "complex_password_at_least_32_characters_long", 34 // secure: true should be used in production (HTTPS) but can't be used in development (HTTP) 35 cookieOptions: { 36 secure: process.env.NODE_ENV === "production", 37 }, 38 }; 39 }, 40);
Creates an express middleware that adds a session
object to the request.
1import { ironSession } from "iron-session/express"; 2 3app.use(ironSession(ironOptions));
Saves the session and sets the cookie header to be sent once the response is sent.
1await req.session.save();
Empties the session object and sets the cookie header to be sent once the response is sent. The browser will then remove the cookie automatically.
You don't have to call req.session.save()
after calling req.session.destroy()
. The session is saved automatically.
This makes your sessions stateless: you do not have to store session data on your server. You do not need another server or service to store session data. This is particularly useful in serverless architectures where you're trying to reduce your backend dependencies.
There are some drawbacks to this approach:
iron-session
to accept basic auth or bearer token methods too. Open an issue if you're interested.iron-session
cookie containing {user: {id: 100}}
is 265 bytes signed and encrypted: still plenty of available cookie space in here.Now that you know the drawbacks, you can decide if they are an issue for your application or not. More information can also be found on the Ruby On Rails website which uses the same technique.
Not so much:
Depending on your own needs and preferences, iron-session
may or may not fit you.
✅ Production ready and maintained.
Thanks to Hoang Vo for advice and guidance while building this module. Hoang built next-connect and next-session.
Thanks to hapi team for creating iron.
Thanks goes to these wonderful people (emoji key):
John Vandivier 💻 🤔 💡 | searchableguy ⚠️ 📖 💻 | Divyansh Singh 💻 📖 🤔 |
This project follows the all-contributors specification. Contributions of any kind welcome!
No vulnerabilities found.
Reason
no dangerous workflow patterns detected
Reason
no binaries found in the repo
Reason
license file detected
Details
Reason
Found 1/30 approved changesets -- score normalized to 0
Reason
1 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
detected GitHub workflow tokens with excessive permissions
Details
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
dependency not pinned by hash detected -- score normalized to 0
Details
Reason
security policy file not detected
Details
Reason
project is not fuzzed
Details
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
Reason
14 existing vulnerabilities detected
Details
Score
Last Scanned on 2025-06-30
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More