Next.js cookbook · Advanced
Create encrypted session cookies in Next.js
Implement login sessions without a session store, using a signed/encrypted cookie readable only on the server.
Approach
After verifying credentials in a Server Action, create a JWT with jose (signed with a strong secret), store it in an HttpOnly; Secure; SameSite=Lax cookie with an expiry, and decrypt/verify it in a data-access helper. Keep the payload minimal (user ID, expiry), refresh it on activity, and delete it on logout. Use a database session instead if you need server-side revocation.
Solution
// lib/session.ts
import "server-only";
import { SignJWT, jwtVerify } from "jose";
import { cookies } from "next/headers";
const key = new TextEncoder().encode(process.env.SESSION_SECRET); // >= 32 random bytes
const MAX_AGE = 7 * 24 * 60 * 60;
export async function createSession(userId: string) {
const token = await new SignJWT({ userId })
.setProtectedHeader({ alg: "HS256" })
.setIssuedAt()
.setExpirationTime(`${MAX_AGE}s`)
.sign(key);
(await cookies()).set("session", token, {
httpOnly: true, secure: true, sameSite: "lax", path: "/", maxAge: MAX_AGE,
});
}
export async function decryptSession(token: string | undefined) {
if (!token) return null;
try {
const { payload } = await jwtVerify(token, key, { algorithms: ["HS256"] });
return payload as { userId: string; exp: number };
} catch {
return null;
}
}
export async function deleteSession() {
(await cookies()).delete("session");
}
// app/login/actions.ts
"use server";
export async function login(_prev: { error?: string }, formData: FormData) {
const user = await verifyCredentials(String(formData.get("email")), String(formData.get("password")));
if (!user) return { error: "Invalid email or password" };
await createSession(user.id);
redirect("/dashboard");
}
Pitfalls
- Storing roles/permissions in the cookie and trusting them forever.
- No way to revoke a stolen stateless session (use short expiry or DB sessions).
- Weak or committed
SESSION_SECRET.
Variations
- Database sessions (session ID cookie + table) for revocation and 'log out everywhere'.
- Auth.js / Better Auth handle sessions, OAuth providers and CSRF for you.
Further reading: Next.js: Session management
Reference: https://nextjs.org/docs/app/guides/authentication#session-management
More Next.js recipes
- Add a Content Security Policy with nonces in Next.js
- Add dark mode to Next.js without a flash
- Add SEO metadata, Open Graph and sitemaps
- Avoid data-fetching waterfalls in Server Components
- Build a validated JSON API with Route Handlers
- Handle errors and 404s in the App Router
- Handle forms with Server Actions and validation
- Keep secrets and server code out of the client bundle