Session Management Libraries

The libraries covered in Authentication Libraries handle the whole problem: verifying who a user is and remembering that fact across requests. Session management libraries solve a narrower problem — they only handle the second half. They give you a secure, ready-made mechanism for persisting arbitrary data (a user id, a role, a cart, anything) in a cookie across requests, but they have no opinion about how that data got there. You're still the one who checks a password or validates an OAuth callback; the library just takes over from the moment you know who the visitor is.

That narrower scope is the whole appeal: no user table to design, no provider configuration, no login UI to build or theme — just a small, auditable amount of code standing between your app and a correctly-sealed cookie.

iron-session

iron-session is the most widely used library in this category for Next.js apps. It seals (encrypts and signs) a plain JavaScript object into the cookie itself using a password you provide, so the session is entirely stateless — there's no database row to look up, and the cookie is the session. Because the payload is encrypted, not just signed, its contents aren't readable by the client the way an unencrypted JWT's claims would be. Reading and writing the session in a Server Action or Route Handler is a single function call around the same cookies() API described in Session Management.



Roll-your-own patterns

Because the underlying primitive — seal an object, store it in an httpOnly cookie, unseal it on the next request — is fairly small, plenty of projects build their own thin wrapper instead of adding a dependency, using a general-purpose sealing library (or the same jose package used for JWTs) directly against the cookies() API. The tradeoff is the same as anywhere else in security code: a well-reviewed, widely-used library has had far more scrutiny than a one-off implementation.

Store-backed variants are also common: instead of sealing the full payload into the cookie, the cookie holds only a random session id, and the actual data lives in Redis or a database table, following the "opaque session token" shape described in Session Management. This trades the simplicity of a fully stateless cookie for the ability to revoke or inspect active sessions server-side.



How this differs from a full auth library

Provides identity verification (passwords, OAuth)No — you write that part yourself before calling the library.
Provides login/sign-up UINo.
Provides a user database or adapterNo.
Provides secure cookie sealing and storageYes — this is the entire job.
Typical dependency footprintSmall, single-purpose.
Reading and writing a sealed session with iron-session inside a Server Action. password must be at least 32 characters and, like any other session secret, belongs in an environment variable.
lib,session.ts:
import { getIronSession } from 'iron-session'
import { cookies } from 'next/headers'

export interface SessionData {
  userId?: string
  isLoggedIn: boolean
}

export const sessionOptions = {
  password: process.env.SESSION_SECRET as string,
  cookieName: 'app-session',
  cookieOptions: {
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax' as const,
  },
}

export async function getSession() {
  return getIronSession<SessionData>(await cookies(), sessionOptions)
}

app,actions.ts:
'use server'

import { getSession } from '@/lib/session'
import { verifyPassword } from '@/lib/password'
import { db } from '@/lib/db'

export async function login(formData: FormData) {
  const email = formData.get('email') as string
  const password = formData.get('password') as string

  const account = await db.user.findUnique({ where: { email } })
  if (!account || !(await verifyPassword(password, account.passwordHash))) {
    return { error: 'Invalid email or password.' }
  }

  const session = await getSession()
  session.userId = account.id
  session.isLoggedIn = true
  await session.save() // seals the object and sets the cookie
}

export async function logout() {
  const session = await getSession()
  session.destroy()
}