Session and Token-Based Authentication
Now that you have a secure User
model, the next task at “Innovate Inc.” is to manage a user’s state after they log in. How does the application remember who they are as they navigate from one page to another? Your senior developer has asked you to investigate and implement a modern, stateless authentication strategy that will scale well as the company grows. This lesson explores two common approaches: traditional sessions and modern token-based authentication with JSON Web Tokens (JWTs).
Stateful vs. Stateless Authentication
At the heart of managing user logins is the concept of “state.” Does the server need to remember (keep a stateful record of) every logged-in user, or can it operate without that memory (statelessly)?
Stateful Authentication (Sessions)
The traditional approach is to use sessions. Here’s how it works:
- A user logs in with their credentials.
- The server verifies the credentials and creates a “session” record, storing it in server memory or a database.
- The server sends a unique Session ID back to the user’s browser, which stores it in a cookie.
- On every subsequent request, the browser sends the Session ID cookie back to the server.
- The server looks up the session record using the ID to identify the user and their permissions.
Pros: Session data is stored on the server, making it secure from client-side tampering.
Cons: This model struggles with scalability. If you have multiple servers, you need a shared session store so that any server can handle any user’s request. This adds complexity and a potential single point of failure.
Stateless Authentication (Tokens)
The modern, preferred approach for APIs is stateless authentication using tokens.
- A user logs in with their credentials.
- The server verifies the credentials and generates a token — a self-contained string of data containing user information.
- This token is “signed” with a secret key known only to the server.
- The server sends the token back to the client. The client is responsible for storing it (e.g., in
localStorage
). - On every subsequent request, the client includes the token in the
Authorization
header. - The server receives the token, verifies its signature to ensure it hasn’t been tampered with, and then reads the user information from it. The server does not need to look up anything in a database to identify the user.
Pros: Highly scalable, as any server with the secret key can verify a token. It’s “stateless” because the server doesn’t need to store any session information.
Cons: Tokens must be stored securely on the client. Since they are self-contained, they cannot be easily revoked before their expiration.
For the “Innovate Inc.” portal, a stateless JWT approach is the best choice.
Introduction to JSON Web Tokens (JWTs)
A JSON Web Token (JWT) is a compact, URL-safe standard for creating access tokens. A JWT is just a string with three parts, separated by dots (.
):
xxxxx.yyyyy.zzzzz
- Header: Metadata about the token, such as the signing algorithm.
- Payload: The data “claims” about the user (e.g., user ID, username).
- Signature: A cryptographic signature to verify the token’s integrity.
JWT Decoded
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Header:
{
“alg”: “HS256”,
“typ”: “JWT”
}
Payload:
{
“sub”: “1234567890”,
“name”: “John Doe”,
“iat”: 1516239022
}
Signature:
HMACSHA256(
base64UrlEncode(header) + ”.” +
base64UrlEncode(payload),
your-256-bit-secret
)
The Payload contains the claims. These are statements about an entity (typically, the user) and additional data. Do not put sensitive information in the payload, as it is only Base64Url encoded, not encrypted, and can be easily read by anyone. The signature ensures that the payload has not been tampered with.
Implementing JWTs with jsonwebtoken
The jsonwebtoken
library is the standard for working with JWTs in Node.js.
First, install it:
npm install jsonwebtoken
Creating (Signing) a Token
When a user successfully logs in, you create a token for them. The jwt.sign()
method takes a payload, a secret key, and optional settings (like an expiration time).
import jwt from 'jsonwebtoken';
const secret = process.env.JWT_SECRET;
const expiration = '2h'; // Token will be valid for 2 hours
// In your user login logic...
const user = await User.findOne({ email: req.body.email });
// ... (password verification logic) ...
// The payload should contain non-sensitive user data
const payload = {
_id: user._id,
username: user.username,
email: user.email,
};
const token = jwt.sign({ data: payload }, secret, { expiresIn: expiration });
// Example token: eyJhbGciOi...
// Send the token back to the client
res.json({ token, user });
Verifying a Token
When the client sends the token back with a request, you need to verify it. The jwt.verify()
method checks the signature and expiration. If the token is invalid or expired, it will throw an error.
import jwt from 'jsonwebtoken';
const secret = process.env.JWT_SECRET;
// In a middleware function that protects routes...
let token = req.headers.authorization;
// "Bearer <token>"
if (token) {
token = token.split(' ').pop().trim();
}
if (!token) {
return res.status(401).json({ message: 'No token found!' });
}
try {
// The decoded object will contain the original payload ({ data: { _id, ... } })
const { data } = jwt.verify(token, secret, { maxAge: '2h' });
req.user = data; // Attach user data to the request object
} catch {
return res.status(401).json({ message: 'Invalid token!' });
}
// Proceed to the next middleware or route handler
next();
Activity 1: Create and Decode a JWT
- In a new Node.js script, install and import
jsonwebtoken
. - Define a sample payload object with some user data (e.g.,
_id
,username
). - Define a secret key string.
- Use
jwt.sign()
to create a token that expires in1h
. Log the token to the console. - Use
jwt.verify()
with the correct secret to decode the token. Log the decoded payload. - Try to verify the token with the wrong secret inside a
try...catch
block. Log the error to see whatjsonwebtoken
returns for an invalid signature.
Knowledge Check
What is the primary advantage of token-based (stateless) authentication over session-based (stateful) authentication?
- Select an answer to view feedback.
Which part of a JWT ensures that the token has not been tampered with?
- Select an answer to view feedback.
Where should you store highly sensitive information, like a user's credit card number?
- Select an answer to view feedback.
Summary
In this lesson, you explored the differences between stateful (session-based) and stateless (token-based) authentication, concluding that JWTs are the superior choice for modern, scalable applications. You learned that a JWT is a signed, self-contained token composed of a header, payload, and signature. We used the jsonwebtoken
library to create (sign
) and validate (verify
) tokens, which are the fundamental operations for securing an API. You are now prepared to protect API endpoints by requiring a valid JWT for access.
References
- jwt.io - A fantastic resource for inspecting and learning about JWTs.
- RFC 7519: JSON Web Token (JWT)