Authentication Explained: Sessions, Tokens, and OAuth
- Contributor
- Nov 22, 2025
- 5 min read
You type your username and password. You click "Log In." The next page knows who you are. How?
HTTP is stateless — every request is independent, with no memory of previous requests. The server doesn't inherently know that the person requesting the dashboard is the same person who logged in 30 seconds ago. Authentication is the mechanism that bridges this gap.
There are three main approaches, each with different tradeoffs. Understanding them helps you choose the right one and understand what's happening when you integrate with authentication systems.
Approach 1: Session-Based Authentication
The oldest and simplest approach.
How It Works
You send your username and password to the server.
The server verifies them against the database.
The server creates a session — a record that says "this user is logged in" — and stores it (typically in memory or a database).
The server sends you a session ID in a cookie.
On every subsequent request, your browser automatically sends the cookie.
The server looks up the session ID, finds the session record, and knows who you are.
Browser Server
| |
|-- POST /login (user, pass) -->|
| |-- Verify credentials
| |-- Create session {id: abc123, user: jane}
|<-- Set-Cookie: session=abc123 -|
| |
|-- GET /dashboard |
| Cookie: session=abc123 --> |
| |-- Look up session abc123
| |-- Found: user is jane
|<-- Dashboard for jane --------|
Strengths
Simple. The concept is straightforward and well-supported by every web framework.
Easy to invalidate. Want to log someone out? Delete their session from the server. Instantly revoked.
Cookies are automatic. The browser sends the session cookie with every request. No client-side code needed.
Limitations
Server state. The server must store every active session. With millions of users, session storage becomes a scaling concern.
Cross-server coordination. If you have multiple servers behind a load balancer, they all need access to the same session store (typically Redis or a database).
Cookies are browser-only. Mobile apps and third-party API consumers don't have a browser cookie jar.
When to Use It
Server-rendered web applications where all users access the application through a browser. If your app is a traditional web app (Django, Rails, ASP.NET MVC) with server-side rendering, session-based auth is the natural choice.
Approach 2: Token-Based Authentication (JWT)
The modern approach for APIs and single-page applications.
How It Works
You send your username and password to the server.
The server verifies them.
The server creates a JSON Web Token (JWT) — a digitally signed piece of data that contains your user information — and sends it back.
You store the token (typically in memory or local storage).
On every subsequent request, you include the token in the Authorization header.
The server verifies the token's signature (without looking anything up) and extracts your user information.
Browser/App Server
| |
|-- POST /login (user, pass) -->|
| |-- Verify credentials
| |-- Create JWT (signed)
|<-- { token: "eyJ..." } -------|
| |
|-- GET /dashboard |
| Authorization: Bearer eyJ.. |
| |-- Verify JWT signature
| |-- Extract user from JWT payload
|<-- Dashboard for jane --------|
What's Inside a JWT
A JWT has three parts, separated by dots:
eyJhbGci... . eyJzdWIi... . SflKxwRJ...
[Header] [Payload] [Signature]
The payload contains claims — data about the user:
{
"sub": "user_42",
"name": "Jane",
"role": "admin",
"exp": 1711000000
}
The signature is a cryptographic hash that proves the token hasn't been tampered with. The server can verify the signature using its secret key without any database lookup.
Strengths
Stateless. The server doesn't store sessions. Everything needed to verify identity is in the token itself. This scales effortlessly across multiple servers.
Works everywhere. Mobile apps, SPAs, third-party API consumers — anything that can send an HTTP header can use JWT.
Self-contained. The token carries user information (name, role, permissions), reducing database lookups on every request.
Limitations
Hard to invalidate. Once issued, a JWT is valid until it expires. You can't "log someone out" by deleting a server-side record — the token is still valid. Mitigation: short expiration times (15-60 minutes) plus refresh tokens.
Size. JWTs are larger than session IDs. Every request carries the full token, which adds bandwidth.
Complexity. Refresh token flows, token storage decisions, and signature verification add complexity that session cookies handle automatically.
When to Use It
APIs consumed by mobile apps, single-page applications, or third-party clients. If your frontend and backend are separate applications (React app calling an API), tokens are the standard approach.
Approach 3: OAuth 2.0 — "Log in with Google/GitHub/etc."
OAuth isn't an alternative to sessions or tokens — it's a protocol for delegated authorization. It lets users log into your application using an account they already have (Google, GitHub, Microsoft) instead of creating a new username and password.
How It Works (Simplified)
User clicks "Log in with Google."
Your app redirects the user to Google's login page.
The user logs in with Google (you never see their password).
Google asks: "This app wants access to your name and email. Allow?"
User approves. Google redirects back to your app with an authorization code.
Your server exchanges the code with Google for an access token.
Your server uses the access token to fetch the user's name and email from Google.
Your server creates a session or JWT for the user (from here, it's approach 1 or 2).
User Your App Google
| | |
|-- Click login->| |
| |-- Redirect --->|
| | |-- Show Google login
|-- Login -------|--------------->|
| | |-- "Allow this app?"
|-- Approve -----|--------------->|
| |<- Code --------|
| |-- Exchange --->|
| |<- Token -------|
| |-- Get profile->|
| |<- Name, email -|
|<-- Logged in --| |
Strengths
No passwords to manage. You don't store passwords. Google handles authentication security. This eliminates password reset flows, password hashing, and credential stuffing attacks against your database.
User convenience. One less account to create. One less password to remember. Higher conversion on signup flows.
Trust. Users trust Google's login more than a form on a site they've never visited.
Limitations
Dependency on the provider. If Google's auth service is down, your users can't log in.
Complexity. OAuth flows have multiple steps, multiple redirects, and subtle security requirements (PKCE, state parameters, token validation) that are easy to get wrong.
Not a complete solution. OAuth handles "who is this person?" but not "what can they do in my app?" You still need authorization logic.
When to Use It
Any user-facing application where reducing signup friction matters. You'll almost always combine OAuth with sessions or JWTs — OAuth handles the initial authentication, and then your app issues its own session/token for subsequent requests.
Don't Build Your Own
For all three approaches: use a library or service, don't implement from scratch. Authentication is a security-critical system with decades of attack vectors — CSRF, XSS token theft, timing attacks, credential stuffing, session fixation.
Libraries (Passport.js, NextAuth, ASP.NET Core Identity) and services (Auth0, Clerk, Firebase Auth) handle these attacks. Your hand-rolled implementation probably doesn't.
Quick Decision Guide
| Situation | Approach | |-----------|----------| | Server-rendered web app, one server | Sessions | | SPA or mobile app calling an API | JWT | | Multiple third-party API consumers | JWT | | "Log in with Google" flow | OAuth + Sessions/JWT | | Need to revoke access instantly | Sessions (or JWT + token blacklist) | | Need stateless scaling | JWT |
Key Takeaway
Sessions store login state on the server and use cookies to identify requests — simple, easy to revoke, browser-only. JWTs are self-contained tokens that carry user data — stateless, work everywhere, harder to revoke. OAuth delegates authentication to a trusted provider like Google — no passwords to manage, higher trust, more complexity. For most applications, use OAuth for social login, JWTs for API authentication, and an auth library or service instead of building your own.


