# JWT Authentication in Node.js Explained Simply


Apps need to know who's using them. But checking a database on every request is slow. JWT lets you prove who you are with a signed token the server can verify instantly.

This is about understanding JWT and building login systems without session databases.

* * *

## What Authentication Means

Authentication answers: "Who are you?"

### Real Example

Checking into a hotel: you show an ID once, and they don't check the guest list every time you walk in. The ID is your proof.

Apps work the same way.

### Without Authentication

```javascript
app.get("/profile", (req, res) => {
  res.json({ message: "Anyone can access this" });
});
```

No protection.

### With Authentication

```javascript
app.get("/profile", (req, res) => {
  if (!req.user) {
    return res.status(401).json({ error: "Not authenticated" });
  }
  res.json({ message: `Hello ${req.user.name}` });
});
```

Only authenticated users see it.

* * *

## What JWT Is

JWT (JSON Web Token) is a token that represents a user's identity.

### The Concept

1. User logs in
2. Server creates a token containing their info
3. Server sends the token
4. User sends the token with every request
5. Server verifies the token (no database needed)

The token itself proves who they are.

### Simple Analogy

Concert wristband: instead of the venue checking a list every time you enter, you wear a wristband proving you're authorized. The wristband is the proof.

JWT is like that wristband for API requests.

### JWT vs Sessions

| Sessions | JWT |
| --- | --- |
| Stored on server | Stored on client |
| Database lookup | Token contains data |
| One server only | Works across servers |
| Can revoke immediately | Valid until expiration |

* * *

## Structure of a JWT

A JWT has three parts: `header.payload.signature`

### Part 1: Header

```javascript
{
  "alg": "HS256",    // Algorithm
  "typ": "JWT"       // Type
}
```

Base64 encoded: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9`

### Part 2: Payload

```javascript
{
  "userId": 1,
  "email": "alice@example.com",
  "name": "Alice",
  "iat": 1234567890,    // Issued at
  "exp": 1234571490     // Expires at
}
```

Base64 encoded: `eyJ1c2VySWQiOjEsImVtYWlsIjoiYWxpY2VAZXhhbXBsZS5jb20ifQ`

### Part 3: Signature

Server signs the header and payload:

```
HMACSHA256(
  base64(header) + "." + base64(payload),
  "secret-key"
)
```

Result: `SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c`

### Complete Token

```
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjE...SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
```

Three parts separated by dots.

### Why the Signature Matters

It proves the token hasn't been tampered with:

```plaintext
Valid token: signature matches
Tampered token: signature doesn't match → rejected
```

* * *

## JWT Token Structure Visualization

```plaintext
JWT Token
  |
  ├─ Header (base64)
  |   ├─ Algorithm: HS256
  |   └─ Type: JWT
  |
  ├─ Payload (base64)
  |   ├─ userId: 1
  |   ├─ email: alice@example.com
  |   ├─ name: Alice
  |   ├─ iat: 1234567890
  |   └─ exp: 1234571490
  |
  └─ Signature (HMAC)
      └─ Hash of header + payload with secret
```

The server can read the payload without a database because it's right there in the token.

* * *

## Login Flow Using JWT

### Step 1: User Logs In

```javascript
app.post("/login", (req, res) => {
  const { email, password } = req.body;
  const user = users.find(u => u.email === email);
  
  if (!user || user.password !== password) {
    return res.status(401).json({ error: "Invalid credentials" });
  }
  
  // Credentials valid
  res.json({ message: "Login successful" });
});
```

### Step 2: Server Creates JWT

```javascript
const jwt = require("jsonwebtoken");
const SECRET = "your-secret-key";

app.post("/login", (req, res) => {
  const { email, password } = req.body;
  const user = users.find(u => u.email === email);
  
  if (!user || user.password !== password) {
    return res.status(401).json({ error: "Invalid credentials" });
  }
  
  // Create token
  const token = jwt.sign(
    { userId: user.id, email: user.email, name: user.name },
    SECRET,
    { expiresIn: "24h" }
  );
  
  res.json({ token });
});
```

Token contains user info and expires in 24 hours.

### Step 3: Client Stores Token

```javascript
// After login, client stores token
localStorage.setItem("token", token);
```

### Step 4: Client Sends Token With Requests

```javascript
// Every request includes the token
const token = localStorage.getItem("token");

fetch("http://localhost:3000/profile", {
  headers: {
    "Authorization": `Bearer ${token}`
  }
});
```

### Step 5: Server Verifies Token

```javascript
app.get("/profile", (req, res) => {
  const token = req.headers.authorization?.split(" ")[1];
  
  if (!token) {
    return res.status(401).json({ error: "No token" });
  }
  
  try {
    const decoded = jwt.verify(token, SECRET);
    res.json({ message: `Hello ${decoded.name}` });
  } catch (error) {
    res.status(401).json({ error: "Invalid token" });
  }
});
```

No database needed. The token proves who they are.

* * *

## JWT Login Authentication Flow

```plaintext
User sends login request
(email + password)
      |
      v
Server finds user in database
      |
      v
Server checks password
      |
      ├─ Wrong: send error
      └─ Correct: continue
      |
      v
Server creates JWT token
(contains user info)
      |
      v
Server sends token to client
      |
      v
Client stores token
(localStorage, sessionStorage, etc)
      |
      v
User makes API request
(includes token in Authorization header)
      |
      v
Server verifies token signature
      |
      ├─ Invalid: reject (401)
      └─ Valid: continue
      |
      v
Request allowed, response sent
```

* * *

## Sending Token With Requests

### In HTTP Header (Standard)

```javascript
// Client side
const token = localStorage.getItem("token");

fetch("http://localhost:3000/profile", {
  headers: {
    "Authorization": `Bearer ${token}`
  }
});
```

This is the standard approach.

### In Cookie

Alternative (automatically sent):

```javascript
// Server side
res.cookie("token", token, {
  httpOnly: true,
  secure: true,
  maxAge: 24 * 60 * 60 * 1000
});
```

### In Query String

Not recommended (exposes token in logs):

```javascript
fetch("http://localhost:3000/profile?token=" + token);
```

The header approach is best.

* * *

## Protecting Routes Using Tokens

### Token Verification Middleware

```javascript
function verifyToken(req, res, next) {
  const token = req.headers.authorization?.split(" ")[1];
  
  if (!token) {
    return res.status(401).json({ error: "No token" });
  }
  
  try {
    const decoded = jwt.verify(token, SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(401).json({ error: "Invalid token" });
  }
}
```

### Apply to Routes

```javascript
// Protected route
app.get("/profile", verifyToken, (req, res) => {
  res.json({ 
    message: `Hello ${req.user.name}`,
    user: req.user
  });
});

// Another protected route
app.get("/posts", verifyToken, (req, res) => {
  res.json({ posts: "User's posts" });
});

// Public route (no middleware)
app.get("/public", (req, res) => {
  res.json({ message: "Anyone can see this" });
});
```

The middleware checks the token before the handler runs.

### Admin-Only Route

```javascript
function requireAdmin(req, res, next) {
  if (req.user.role !== "admin") {
    return res.status(403).json({ error: "Admin only" });
  }
  next();
}

app.delete("/users/:id", verifyToken, requireAdmin, (req, res) => {
  res.json({ message: "User deleted" });
});
```

* * *

## Complete Login Example

```javascript
const express = require("express");
const jwt = require("jsonwebtoken");

const app = express();
app.use(express.json());

const SECRET = "your-secret-key";

// In-memory user store
const users = [
  { id: 1, email: "alice@example.com", password: "password123", name: "Alice" },
  { id: 2, email: "bob@example.com", password: "password456", name: "Bob" }
];

// Login endpoint
app.post("/login", (req, res) => {
  const { email, password } = req.body;
  
  // Find user
  const user = users.find(u => u.email === email);
  
  // Check password
  if (!user || user.password !== password) {
    return res.status(401).json({ error: "Invalid credentials" });
  }
  
  // Create token
  const token = jwt.sign(
    { userId: user.id, email: user.email, name: user.name },
    SECRET,
    { expiresIn: "24h" }
  );
  
  res.json({ token });
});

// Verify token middleware
function verifyToken(req, res, next) {
  const token = req.headers.authorization?.split(" ")[1];
  
  if (!token) {
    return res.status(401).json({ error: "No token" });
  }
  
  try {
    const decoded = jwt.verify(token, SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(401).json({ error: "Invalid token" });
  }
}

// Protected route
app.get("/profile", verifyToken, (req, res) => {
  res.json({ message: `Hello ${req.user.name}`, user: req.user });
});

// Public route
app.get("/public", (req, res) => {
  res.json({ message: "Public data" });
});

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});
```

### Test It

```bash
# Login
curl -X POST http://localhost:3000/login \
  -H "Content-Type: application/json" \
  -d '{"email":"alice@example.com","password":"password123"}'

# Response: {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}

# Access protected route with token
curl http://localhost:3000/profile \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# Response: {"message":"Hello Alice","user":{"userId":1,...}}
```

* * *

## Practice Assignment

**1. Create a login endpoint:**

```javascript
// Receive email and password
// Find user in array
// Create JWT if credentials valid
// Return token
```

**2. Build token verification middleware:**

```javascript
// Extract token from Authorization header
// Verify signature with jwt.verify()
// Store decoded user in req.user
// Call next() if valid
```

**3. Protect a route:**

```javascript
// Apply verifyToken middleware to a route
// Route should access req.user
// Test with curl including token
```

**4. Handle token expiration:**

```javascript
// Set expiresIn to "1h"
// Try accessing route after expiration
// See "invalid token" error
```

* * *

## Quick Recap

* **Authentication** proves who someone is.
  
* **JWT** is a token containing user info, signed by the server.
  
* JWT has three parts: `header.payload.signature`
  
* **Header** specifies the algorithm.
  
* **Payload** contains user data.
  
* **Signature** proves the token hasn't been tampered with.
  
* Login: receive credentials → verify → create token → send to client.
  
* Clients send the token in the `Authorization` header.
  
* Servers verify the token's signature, no database needed.
  
* Protect routes by checking the token before allowing access.
  
* JWT is stateless: the token proves who the user is.

JWT makes authentication simple and scalable.

---

> If you enjoyed this article, check out my other blogs on this profile.
> 🔗 Connect with me: [LinkedIn](https://linkedin.com/in/rajharsh03) | [GitHub](https://github.com/RajHarsh03) | [X (Twitter)](https://x.com/rajharsh03)
