R
Rishtaara
Next.js: Zero to Full-Stack
Lesson 30 of 42Article16 min

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
Tip: Do not put secrets or roles you cannot change in a long-lived JWT. Prefer short-lived tokens and re-check permissions on sensitive actions.
Auth.js session strategy (concept)
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
Reading a cookie in a Route Handler
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).

Tip: Add Content-Security-Policy after you know every script and font host you need. A too-strict CSP can break analytics or embeds — tighten step by step.
next.config.ts — starter security headers
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;