JWT, Sessions & Security Basics
A JWT (JSON Web Token) is a signed string that carries user claims (id, email, expiry). The server verifies the signature without looking up a session row every time.
JWT vs database sessions
A JWT (JSON Web Token) is a signed string that carries user claims (id, email, expiry). The server verifies the signature without looking up a session row every time.
A database session stores a session id in a cookie and the full session data in the DB. You can revoke it instantly by deleting the row.
Auth.js supports both strategies. JWT is simpler to start; database sessions are better when you need instant logout everywhere or admin revoke.
Real-life example: A JWT is a stamped festival wristband — security checks the stamp, not a guest list. A DB session is a hotel key card that the front desk can deactivate in one click.
- JWT — no DB round-trip; harder to revoke early unless you keep a denylist
- Database session — easy revoke; needs storage and cleanup of old rows
- Always set a short expiry and refresh wisely
export const { handlers, auth } = NextAuth({
session: {
strategy: "jwt", // or "database" with an adapter
maxAge: 60 * 60 * 24 * 7, // 7 days
},
providers: [/* ... */],
});Cookies overview
Session cookies travel with every request to your domain. Important flags: HttpOnly (JS cannot read it), Secure (HTTPS only), SameSite (limits cross-site sending).
Auth.js sets these flags for you when configured correctly in production.
Real-life example: HttpOnly cookies are a locker key locked inside the hotel safe — your JavaScript hands cannot pick it up, so XSS thieves struggle more.
- HttpOnly — blocks document.cookie access
- Secure — cookie only on HTTPS
- SameSite=Lax or Strict — reduces CSRF risk
- Path and Domain — control where the cookie is sent
import { cookies } from "next/headers";
export async function GET() {
const jar = await cookies();
const theme = jar.get("theme")?.value ?? "light";
return Response.json({ theme });
}Security headers intro
Security headers tell the browser how to treat your site: block embedding (clickjacking), control scripts, force HTTPS.
You can set them in next.config or in middleware. Start with a few safe defaults, then tighten Content-Security-Policy as you learn.
Real-life example: Security headers are house rules on the front door — no framing our windows into another building (X-Frame-Options), prefer the locked entrance (HSTS).
import type { NextConfig } from "next";
const securityHeaders = [
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
{
key: "Permissions-Policy",
value: "camera=(), microphone=(), geolocation=()",
},
];
const nextConfig: NextConfig = {
async headers() {
return [{ source: "/:path*", headers: securityHeaders }];
},
};
export default nextConfig;