Guide to NextAuth: Stateless(default) and Stateful
Prerequisites
Solid understanding of Authentication fundamentals
Familiarity with JWT, Sessions, OAuth
If you want to read about above topics checkout this article https://noncodersuccess.medium.com/understanding-session-jwt-token-sso-and-oauth-2-0-c166b843fb1c
This article does not focus on exact configurations or line-by-line code — those are well documented and easy to find with a quick search. But rather we will be looking into the High level working and the different ways to integrate it based on our requirements.
Stateless vs Stateful — What Do We Mean?
Stateless Authentication
No persistent session data stored on the server
All required identity data is embedded inside a JWT
Server validates the JWT
Source of truth: JWT (identity) + Database (authorization)
Stateful Authentication
Session information is stored on the server
Client only holds a session identifier
Server fetches session data on every request
Requires shared storage (DB / Redis) for scaling
Source of truth: Server-side session store
1. Credential Verification
User Logins by Username & password or OAuth(link to OAuth Article)
Users can authenticate using any method like:
Username & Password
OAuth providers (Google, GitHub, etc.)
We will be use the example of OAuth
1.1 User initiates login
User clicks “Sign in with Google”
1.2 OAuth authentication (external)
Browser → Google OAuth
User authenticates & consents
Google redirects back with
authorization_code
Google confirms identity
I also has a article on OAuth, if you want you can read here : https://rahul-choudhary.hashnode.dev/login-with-google-a-beginners-guide-to-oauth
1.3 User & Account resolution (Prisma Adapter)
NextAuth:
Finds or creates a
UserLinks OAuth identity in
Account
User ← Account (google)
Identity is now internalized.
2. Session Creation
This is where Stateless vs Stateful diverges.
2.1 Stateless (Default in NextAuth)
NextAuth creates a JWT
JWT contains:
sub(user id)email, name
custom claims (optional)
exp,iat
JWT is:
Signed
Encrypted
Stored in an HTTP-only cookie
No database entry is created for the session.
2.2 Stateful Sessions
A session record is created in the database
Cookie contains only a sessionId
Cookie → sessionId → DBsession →User
This requires:
session: {
strategy:"database"
}
JWT session is created
This JWT is set in the cookie
3. Request Handling
For every protected request, NextAuth does:
Reads cookie
Extracts token (JWT or sessionId)
Validates it
Resolves user identity
Server Component
const session =awaitauth()
Client Component
const {data: session } =useSession()
API Route / Server Action
const session =awaitauth()
If session === null → user is unauthenticated.
4. Authorization (Your Responsibility)
Authentication ≠ Authorization.
After auth(), you decide what the user can do.
Two Common Approaches
Option 1: Store role in JWT
Faster
Risk of stale roles
Option 2: Fetch role from DB (Recommended)
const session =awaitauth()
const user =await db.user.findUnique({
where: {id: session.user.id }
})
if (user.role !=="ADMIN") {
thrownewForbiddenError()
}
Database is the source of truth
JWT is only proof of identity, not authority.
5. JWT Expiry (Stateless Boundary)
Every JWT has an exp claim.
Once expired:
JWT is no longertrusted
Requests with expired JWT:
Cannot access protected resources
May still be eligible for renewal
5.1 Silent JWT Refresh (Stateless Renewal)
This happens automatically on the server.
Flow:
Request arrives with expired JWT
NextAuth detects expiry
jwtcallback executesServer checks:
Is session within
maxAge?Does user still exist?
If YES
New JWT generated
Cookie updated
Request continues
If NO
Session rejected
User redirected to login
No refresh token
Client does nothing
5.2 Absolute Session Limit
Example:
JWT expiry:10minutes
Session maxAge:30days
After 30 days:
No silent refresh
Forced re-login
This prevents infinite sessions.
6. Logout
6.1 Logout in Stateless Mode
Logout simply:
Deletes the JWT cookie
That’s it.
No server cleanup required.
6.2 Logout in Stateful Mode
Logout does two things:
Deletes cookie
Deletes session record from DB
import { prisma } from "@/lib/prisma"
await prisma.session.delete({
where: {
id: sessionId
}
})
This allows:
Immediate session invalidation
Central session control
When to Use Stateless Sessions
Best for:
Most Next.js apps
High scalability
Serverless deployments
Simple auth requirements
Pros
No session storage
Easy horizontal scaling
Fast request handling
Cons
Harder to revoke instantly
JWT size grows with claims
When to Use Stateful Sessions
Best for:
Enterprise apps
Strict security requirements
Admin dashboards
Compliance-heavy systems
Pros
Immediate revocation
Centralized session control
Smaller cookies
Cons
Requires DB / Redis
Scaling complexity
Slightly slower per request
