🎫 The JWT Strategy: Stateless Authentication
In the previous lesson, we learned how to safely hash a password and confirm a user's login. But what happens on the next request? When the user clicks "View My Profile", HTTP is mathematically Stateless. The server immediately forgets who the user is the second the login request ends.
To allow users to stay logged in, we use JSON Web Tokens (JWT).
1️⃣ What is a JWT?
A JWT (pronounced "jot") is a digital passport. When a user logs in successfully, the Express server cryptographically signs a passport and hands it to the React frontend. For every future request, React simply flashes the passport to the server.
A JWT is composed of three strings separated by dots:
Header.Payload.Signature
eyJhbGciOiJIUzI1Ni... . eyJ1c2VySWQiOjl9... . SflKxwRJSMeKKF2QT...
- Header: Tells the server what algorithm was used (usually HMAC SHA256).
- Payload: The actual JSON data (e.g.
{ "userId": 9, "role": "admin" }). This is Base64 Encoded, NOT ENCRYPTED! Anyone with an internet connection can read the payload. Never put passwords in here. - Signature: The magic security seal. The server takes the Header, Payload, and a secret 50-character password hidden in your
.envfile, and hashes them together. If a hacker tries to modify theiruserIdfrom 9 to 1 in the Payload, the mathematical Signature breaks instantly, and the server rejects it.
2️⃣ Generating The Token (The Login Route)
npm install jsonwebtoken
import jwt from 'jsonwebtoken';
import express from 'express';
const app = express();
app.post('/api/login', async (req, res) => {
// 1. Verify password (covered in previous lesson)
const user = await verifyPassword(req.body.username, req.body.password);
if (!user) return res.status(401).send();
// 2. Generate the Token
// We embed non-sensitive data into the Payload so we don't have to query the
// database on every single future request!
const payload = {
userId: user.id,
role: user.role
};
// 3. Sign it with the ultra-secret Server Key
const token = jwt.sign(payload, process.env.JWT_SECRET_KEY, {
expiresIn: '2h' // Security: Force the token to self-destruct in 2 hours.
});
// 4. Send the passport to React
res.json({ message: "Logged in", token: token });
});
3️⃣ Verifying The Token (The Auth Middleware)
Now that React has the token, it will attach it to the Authorization header of every fetch() request it makes.
In Express, we build a Global Middleware Shield that intercepts incoming traffic, looks at the passport, verifies the cryptographic signature, and explicitly allows or denies the request.
const requireAuth = (req, res, next) => {
// 1. Extract the token from the "Authorization: Bearer <token>" header
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: "Missing Passport" });
}
const token = authHeader.split(' ')[1];
try {
// 2. Mathematically verify the signature using our Server Key
// If the token is modified or expired, this function THROWS A FATAL ERROR.
const decodedPayload = jwt.verify(token, process.env.JWT_SECRET_KEY);
// 3. Attach the decoded user data directly to the Express Request object!
// Now, every downstream route automatically knows exactly who the user is!
req.user = decodedPayload;
// 4. Everything is safe. Proceed to the actual Route.
next();
} catch (err) {
// Token was fake or expired
return res.status(401).json({ error: "Invalid or Expired Passport" });
}
};
// --- Utilizing the Shield ---
app.get('/api/dashboard', requireAuth, (req, res) => {
// We magically know their ID because the middleware attached it!
res.send(`Welcome to your private dashboard, User #${req.user.userId}`);
});
4️⃣ Storing the Token in React
Where does React keep the token string between page refreshes?
1. LocalStorage (Easy but Vulnerable)
If you use localStorage.setItem('token', token), it survives page refreshes. However, it is vulnerable to XSS (Cross-Site Scripting). If a hacker injects malicious JavaScript into your site, they can easily read localStorage and steal the token.
2. HttpOnly Cookies (Enterprise Standard) Instead of sending the token back as a JSON string, Express can force the token into a deeply secure Browser Cookie.
// Express sending an HttpOnly Cookie
res.cookie('token', token, {
httpOnly: true, // Javascript is physically banned from reading this cookie. Immune to XSS!
secure: true, // Only send over HTTPS encryption.
sameSite: 'strict', // Prevents CSRF attacks
maxAge: 7200000 // 2 hours in ms
});
When using HttpOnly Cookies, the browser automatically attaches the passport to every single outgoing HTTP request. Your React code doesn't even need to know the token exists!
💡 Summary Lexicon
| Concept | Explanation | Rule of Thumb |
|---|---|---|
| Statelessness | Server instantly forgets users after a response. | Need JWTs to fake long-term memory. |
| Payload | The middle section of a JWT. | NEVER put a password or credit card here. It is plain text. |
| Signature | The cryptographic seal. | If the Payload is altered, the Signature mathematically breaks. |
| Middleware | req.user = payload | The middleware cracks open the token, and attaches the user to the req object for the final route. |
| HttpOnly Cookie | The safest storage location. | Prevents XSS script attacks from stealing passports. |