Follow AiTechWorlds on LinkedIn for professional AI content!Follow Now →
18 minLesson 25 of 33
Authentication

Auth Overview: Sessions vs JWT vs OAuth

Auth Overview: Sessions vs JWT vs OAuth

Authentication is one of the most important and most misunderstood parts of web development. Three approaches dominate modern web apps — each with different tradeoffs around security, complexity, and scalability. Understanding which to use and why is essential for building production applications.

The Three Authentication Models

1. Session-Based Authentication (Server Sessions)

The server creates a session and stores it in a database. The browser gets a session ID in a cookie.

1. User logs in with email + password
2. Server verifies credentials
3. Server creates a session record in the database: { sessionId, userId, createdAt, expiresAt }
4. Server sets a cookie: session=abc123
5. Browser sends cookie on every subsequent request
6. Server looks up session in database to identify the user

Pros:

  • Sessions can be invalidated instantly (delete the record)
  • No secret to leak client-side
  • Works well for traditional server-rendered apps

Cons:

  • Database lookup on every request
  • Stateful — doesn't scale horizontally without sticky sessions or a shared session store (Redis)

2. JWT (JSON Web Tokens)

Instead of a session ID that points to data on the server, the token itself contains the user's data, cryptographically signed.

1. User logs in
2. Server creates a JWT: { userId, email, role } signed with a secret key
3. Browser stores the JWT (cookie or localStorage)
4. Browser sends JWT on every request
5. Server verifies the signature — no database lookup needed
6. Server reads the user data directly from the token
JWT structure: header.payload.signature
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOiIxMjMiLCJyb2xlIjoiYWRtaW4ifQ.signature

Pros:

  • Stateless — no database lookup per request
  • Scales horizontally without shared state
  • Works well for microservices and API-first architectures

Cons:

  • Cannot invalidate a token before expiry (logout doesn't actually log out)
  • If a secret is leaked, all tokens are compromised
  • Storing in localStorage is vulnerable to XSS; cookies have CSRF risks

Mitigation for revocation: Use short expiry (15 min) with a refresh token stored in an HttpOnly cookie. The refresh token can be blacklisted in a database if needed.

3. OAuth / Social Login

Delegate authentication to a trusted provider (Google, GitHub, Apple). You never handle passwords yourself.

1. User clicks "Continue with Google"
2. Browser redirects to Google's auth server
3. User authenticates with Google
4. Google redirects back with an authorization code
5. Your server exchanges the code for an access token
6. Your server fetches user info from Google
7. Your server creates or updates a user record
8. Your server creates a session (or JWT) for the user

Pros:

  • You never store passwords
  • Users trust familiar providers
  • Provider handles 2FA, security, password resets
  • Reduces your security attack surface enormously

Cons:

  • Dependency on third-party provider
  • Complex callback flow
  • Users need an account with the provider

Cookies vs localStorage for Token Storage

This comes up constantly. The answer is: HttpOnly cookies.

HttpOnly cookie:
✅ Not accessible via JavaScript (immune to XSS)
✅ Sent automatically with every request
✅ Has SameSite protection against CSRF
✅ Works with server-side auth checks

localStorage:
❌ Accessible by ANY JavaScript on the page
❌ Stolen by XSS attacks
❌ Manual management (add to every fetch call)
⬜ Easier to implement

Store your JWT or session token in an HttpOnly, Secure, SameSite=Lax cookie:

// Set cookie in Node.js/Express
res.cookie("session", token, {
    httpOnly: true,      // not accessible via JS
    secure: true,        // HTTPS only
    sameSite: "lax",     // CSRF protection
    maxAge: 7 * 24 * 60 * 60 * 1000,  // 7 days
});

// Set cookie in Next.js
cookies().set("session", token, {
    httpOnly: true,
    secure: process.env.NODE_ENV === "production",
    sameSite: "lax",
    maxAge: 60 * 60 * 24 * 7,
});

Password Hashing

Never store plaintext passwords. Always hash with bcrypt:

import bcrypt from "bcryptjs";

// Creating a user
const hashedPassword = await bcrypt.hash(password, 12);
// 12 is the salt rounds — higher = slower but more secure (10-12 is standard)
await db.user.create({ data: { email, password: hashedPassword } });

// Verifying a password
const isValid = await bcrypt.compare(inputPassword, storedHash);
if (!isValid) throw new Error("Invalid password");

Never use MD5, SHA1, or SHA256 for passwords. These are fast — bcrypt is intentionally slow, making brute-force attacks infeasible.

What to Use in Next.js

For most Next.js applications:

Just cookies + your own auth logic: Best for full control, learning, or when you want to understand what's happening. More code to write.

NextAuth.js v5 (Auth.js): Excellent for production. Handles OAuth providers, sessions, JWT rotation, and database adapters. Minimal configuration.

Clerk: Hosted auth service. Zero backend auth code. Handles everything including user management UI. Higher monthly cost at scale.

Supabase Auth: If you're already using Supabase for your database, their auth is well-integrated and free to a generous limit.

Which Model Does Next.js App Router Use?

NextAuth.js v5 defaults to JWT sessions stored in cookies. No database required for sessions. If you need to revoke sessions (important for admin-facing apps), configure a database adapter to store sessions.

NextAuth.js flow:
1. User signs in (credentials or OAuth)
2. NextAuth creates a JWT session token
3. Token is set as an HttpOnly cookie
4. Server Components can call auth() to get the session
5. Middleware can check the session before serving routes

Next lesson: NextAuth.js v5 setup — implementing production authentication in 30 minutes.

📱

Get this course's notes on Telegram!

Free cheat sheets, summaries & practice exercises

Get Notes Free →
!