JWT Authentication
# JWT Authentication
Welcome to Chapter 24! In the last chapter, we successfully verified a user's password. But there is a massive problem: HTTP is a stateless protocol.
This means the server has amnesia. It treats every single request as completely independent. If a user logs in, the server says "Success!". But 1 second later, if the user requests their private /dashboard, the server says "Who are you?".
To solve this, we give the user a digital ID card after they log in. Every time they make a new request, they show this ID card to the server. The modern standard for this ID card is a JSON Web Token (JWT).
---
1. Introduction
A JSON Web Token (JWT) is a long, scrambled string of characters generated by your server.
The Workflow:
- 1. Login: User sends email/password. Server verifies with bcrypt.
-
2.
Issue Token: Server uses the
jsonwebtokenpackage to generate a token containing the user's databaseid. It sends this token back to the client.
- 3. Store Token: The frontend (React/Browser) saves this token (usually in LocalStorage).
-
4.
Access Protected Route: The client wants to view
/api/dashboard. They send a GET request and attach the JWT in the request headers.
- 5. Verify Token: The server intercepts the request using a custom Middleware, verifies the token is valid, and lets them in.
---
2. Learning Objectives
By the end of this chapter, you will be able to:
-
Install the
jsonwebtokenpackage.
- Understand the structure of a JWT (Header, Payload, Signature).
- Sign (create) a token when a user successfully logs in.
-
Create an
authMiddlewareto protect private routes.
- Extract and verify a token from incoming request headers.
---
3. Beginner-Friendly Explanations
The Secret Key
To generate a JWT, your server needs a Secret Key (just a long, random string of text like"mySuperSecretKey123").
When the server issues a token, it uses this secret key to "sign" it. When the client sends the token back, the server uses the *same* secret key to verify it. If a hacker tries to forge a fake token, the server will reject it because the hacker doesn't know your secret key.
The Payload
The token holds data inside it (the Payload). Usually, we put the user's databaseid inside the token. That way, when the server verifies the token, it immediately knows exactly *which* user is making the request.
---
4. Syntax Explanation
Let's see how to generate a token inside our Login route.
```javascript id="ch24-syntax-1" // 1. Require the package const jwt = require('jsonwebtoken');
// (Inside your login route, AFTER bcrypt.compare is successful)
// 2. Define the payload (the data you want to embed in the token) const payload = { userId: user._id };
// 3. Define your secret key const SECRET = "mySuperSecretPassword123";
// 4. Sign the token // Arg 1: Payload, Arg 2: Secret, Arg 3: Expiration time const token = jwt.sign(payload, SECRET, { expiresIn: '1h' });
// 5. Send the token back to the user res.status(200).json({ message: "Login successful", token: token });
javascript id="ch24-code-1" // We apply our custom 'verifyToken' middleware (which we write next) app.get('/api/dashboard', verifyToken, (req, res) => { // If the middleware allows them through, they see this: res.json({ message: "Welcome to your private dashboard!" }); });
javascript id="ch24-code-2" const jwt = require('jsonwebtoken'); const SECRET = "mySuperSecretPassword123";
const verifyToken = (req, res, next) => { // 1. Look for the token in the request headers const authHeader = req.header('Authorization'); // 2. If there is no header, reject them if (!authHeader) { return res.status(401).json({ error: "Access Denied. No token provided." }); }
// 3. The header looks like "Bearer eyJhbG...". We split it to just get the token. const token = authHeader.split(' ')[1];
try { // 4. Verify the token using our secret key const decodedPayload = jwt.verify(token, SECRET); // 5. Attach the decoded payload (the userId) to the request object! req.user = decodedPayload; // 6. Let them into the route! next(); } catch (err) { // If token is fake, altered, or expired, it jumps here res.status(403).json({ error: "Invalid or expired token." }); } };
javascript id="ch24-mini-project" const express = require('express'); const jwt = require('jsonwebtoken');
const app = express(); app.use(express.json());
const SECRET = "superSecret123";
// Mock Database User const dbUser = { id: 99, email: "user@test.com", password: "password123" };
// --- 1. LOGIN ROUTE --- app.post('/login', (req, res) => { const { email, password } = req.body;
// Simulate checking credentials if (email === dbUser.email && password === dbUser.password) { // Credentials match! Generate Token const token = jwt.sign({ userId: dbUser.id }, SECRET, { expiresIn: '15m' }); return res.json({ message: "Login Success", token: token }); } res.status(401).json({ error: "Invalid credentials" }); });
// --- 2. AUTH MIDDLEWARE --- const verifyToken = (req, res, next) => { const authHeader = req.header('Authorization'); if (!authHeader) return res.status(401).json({ error: "No token" });
const token = authHeader.split(' ')[1];
try { const decoded = jwt.verify(token, SECRET); req.user = decoded; // Attach { userId: 99 } to req next(); } catch (err) { res.status(403).json({ error: "Invalid token" }); } };
// --- 3. PROTECTED ROUTE --- // Must pass through verifyToken middleware first app.get('/profile', verifyToken, (req, res) => { // We have access to req.user because the middleware attached it! res.json({ message: "Welcome to your profile!", yourId: req.user.userId }); });
app.listen(3000, () => console.log("JWT Server Running"));
``
---
12. Coding Challenges
Challenge 1: Create a second protected route /settings. Apply the verifyToken middleware to it. Verify that a user cannot access it without a token.
Challenge 2: Modify the jwt.sign configuration to make the token expire in 5s (5 seconds). Log in, grab the token, wait 6 seconds, and try to access /profile. Observe the "Invalid token" error because it expired!
---
13. MCQs with Answers
Q1: What does JWT stand for? A) JavaScript Web Transfer B) JSON Web Token C) Java Web Transaction D) JSON Window Tracker Answer: B
Q2: What is the main purpose of a JWT in web development? A) To securely hash passwords in the database. B) To act as a digital ID card to keep users authenticated across stateless HTTP requests. C) To encrypt the database connection. D) To format HTML templates. Answer: B
Q3: Is the Payload of a JWT encrypted and hidden from the public? A) Yes, it is fully encrypted. B) No, it is merely Base64 encoded and can be decoded by anyone. Answer: B
Q4: Where does the client typically place the JWT when sending a request to a protected API route?
A) In the req.body
B) In the URL parameters
C) In the Authorization HTTP header
D) In a hidden HTML input
Answer: C
---
14. Interview Questions
- 1. How does the server know a JWT is authentic and wasn't forged by a hacker?
key known only to the server. When a token is sent back, the server re-runs the algorithm. If the signatures don't match, the server knows the payload was tampered with or forged.
-
2.
Why is it dangerous to put a password in a JWT payload?
*Answer:* The payload is just Base64 encoded, not encrypted. Anyone who intercepts the token (or steals it from the browser) can easily decode the payload and read the password in plain text.
---
15. FAQs
Q: Do I need to use cookies instead?
A: You can! JWTs can be stored in
LocalStorage (accessed via JS) or in HttpOnly Cookies (handled automatically by the browser). Storing JWTs in HttpOnly cookies is technically more secure against Cross-Site Scripting (XSS) attacks, but sending them via headers (as shown here) is standard practice for mobile app APIs and React Single Page Applications.
Q: How do I "logout" a user if I'm using JWT?
A: Because JWTs are stateless, the server doesn't "remember" the token. To log a user out, you simply delete the token from the frontend (e.g.,
localStorage.removeItem('token')). If they don't have the token, they can't access protected routes anymore!
---
16. Summary
-
HTTP is stateless. We use JWTs to maintain user sessions.
-
Use
jwt.sign() during login to generate a token containing the user's _id.
-
The client sends the token back in the
Authorization: Bearer <token> header.
-
Custom Middleware intercepts the request and uses
jwt.verify() to check the token.
-
If valid, the middleware attaches the payload to
req.user and calls next().
---
17. Next Chapter Recommendation
We can authenticate users and handle text data seamlessly. But what if a user wants to upload a profile picture?
express.json` cannot handle image files! In Chapter 25: File Uploads with Multer, we will learn how to intercept incoming files, validate them, and save them to our server's hard drive!