01How The Internet Works 02Your First Node.js Server 03HTTP Methods and JSON 04Express.js Framework 05Building REST APIs 06MongoDB Fundamentals 07Mongoose ODM 08Data Modeling 09Authentication Basics 10JWT Implementation

Settings

Theme
Sand
Cloud
Midnight
Forest
Sunset
Purple
Ocean
Crimson
Font
Merriweather
Inter
JetBrains
Space Grotesk
Fira Code
Playfair
Font Size
100%
Bookmark
No bookmark set
Day 10

JWT Implementation

JSON Web Tokens enable stateless authentication - the server doesn't need to store session data. Today you learn to generate, verify, and use JWTs to protect your API routes.

Understanding JWT

A JWT is a compact, URL-safe token containing claims (data) that can be verified and trusted because it is digitally signed.

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
|_______________________________|______________________________________________________________________|__________________________________|
           HEADER                                              PAYLOAD                                              SIGNATURE

JWT Structure

  • Header - Algorithm (HS256) and token type (JWT)
  • Payload - Claims (user data, expiration, issued at)
  • Signature - Verifies the token hasn't been tampered with
Security Warning

JWT payload is only Base64 encoded, NOT encrypted. Anyone can decode and read it. Never put sensitive data like passwords in the payload.

Terminal
npm install jsonwebtoken

Generating Tokens

Basic JWT Usage
const jwt = require('jsonwebtoken');

// Secret key - in production, use environment variable
// Must be long, random, and kept secure
const JWT_SECRET = 'your-super-secret-key-min-32-chars';

// Generate a token
function generateToken(user) {
  // Payload - data to encode in the token
  const payload = {
    id: user._id,        // User's database ID
    email: user.email,   // User's email
    role: user.role      // User's role (optional)
  };

  // Sign the token with secret and options
  const token = jwt.sign(payload, JWT_SECRET, {
    expiresIn: '1h'      // Token expires in 1 hour
  });

  return token;
}

// Verify a token
function verifyToken(token) {
  try {
    // Returns the decoded payload if valid
    const decoded = jwt.verify(token, JWT_SECRET);
    return decoded;
  } catch (err) {
    // Token is invalid or expired
    return null;
  }
}

Authentication Middleware

middleware/auth.js
const jwt = require('jsonwebtoken');

const JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';

// Middleware to protect routes
function authenticate(req, res, next) {
  // Get token from Authorization header
  // Format: "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
  const authHeader = req.headers.authorization;

  // Check if header exists and has correct format
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({
      error: 'Access denied. No token provided.'
    });
  }

  // Extract token (remove "Bearer " prefix)
  const token = authHeader.split(' ')[1];

  try {
    // Verify token and decode payload
    const decoded = jwt.verify(token, JWT_SECRET);

    // Attach user data to request object
    // Now available in route handlers as req.user
    req.user = decoded;

    // Continue to the next middleware/route handler
    next();
  } catch (err) {
    // Handle different JWT errors
    if (err.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Token expired' });
    }
    return res.status(401).json({ error: 'Invalid token' });
  }
}

// Role-based access control middleware
function authorize(...allowedRoles) {
  return (req, res, next) => {
    // Check if user's role is in the allowed list
    if (!allowedRoles.includes(req.user.role)) {
      return res.status(403).json({
        error: 'Access denied. Insufficient permissions.'
      });
    }
    next();
  };
}

module.exports = { authenticate, authorize };

Complete JWT Authentication System

server.js
const express = require('express');
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');

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

// Configuration - use environment variables in production
const JWT_SECRET = 'your-super-secret-key-change-in-production';
const JWT_EXPIRES_IN = '1h';

// Connect to MongoDB
mongoose.connect('mongodb://localhost:27017/jwtapp');

// User Schema
const userSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true, lowercase: true },
  password: { type: String, required: true, select: false },
  name: { type: String, required: true },
  role: { type: String, enum: ['user', 'admin'], default: 'user' }
});

// Hash password before saving
userSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next();
  this.password = await bcrypt.hash(this.password, 10);
  next();
});

// Password comparison method
userSchema.methods.comparePassword = function(candidatePassword) {
  return bcrypt.compare(candidatePassword, this.password);
};

const User = mongoose.model('User', userSchema);

// Generate JWT token
function generateToken(user) {
  return jwt.sign(
    { id: user._id, email: user.email, role: user.role },
    JWT_SECRET,
    { expiresIn: JWT_EXPIRES_IN }
  );
}

// Authentication middleware
function authenticate(req, res, next) {
  const authHeader = req.headers.authorization;
  if (!authHeader || !authHeader.startsWith('Bearer ')) {
    return res.status(401).json({ error: 'No token provided' });
  }
  const token = authHeader.split(' ')[1];
  try {
    req.user = jwt.verify(token, JWT_SECRET);
    next();
  } catch (err) {
    res.status(401).json({ error: 'Invalid token' });
  }
}

// Admin-only middleware
function adminOnly(req, res, next) {
  if (req.user.role !== 'admin') {
    return res.status(403).json({ error: 'Admin access required' });
  }
  next();
}

// ROUTES

// Register new user
app.post('/api/auth/register', async (req, res) => {
  try {
    const { email, password, name } = req.body;
    const exists = await User.findOne({ email });
    if (exists) {
      return res.status(400).json({ error: 'Email already registered' });
    }
    const user = await User.create({ email, password, name });
    const token = generateToken(user);
    res.status(201).json({ token, user: { id: user._id, email: user.email, name: user.name } });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// Login user
app.post('/api/auth/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    const user = await User.findOne({ email }).select('+password');
    if (!user || !(await user.comparePassword(password))) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }
    const token = generateToken(user);
    res.json({ token, user: { id: user._id, email: user.email, name: user.name } });
  } catch (err) {
    res.status(500).json({ error: 'Login failed' });
  }
});

// Protected route - get current user profile
app.get('/api/profile', authenticate, async (req, res) => {
  const user = await User.findById(req.user.id);
  res.json({ user: { id: user._id, email: user.email, name: user.name, role: user.role } });
});

// Admin-only route - get all users
app.get('/api/admin/users', authenticate, adminOnly, async (req, res) => {
  const users = await User.find().select('-__v');
  res.json({ users });
});

app.listen(3000, () => console.log('Server running on port 3000'));

Refresh Token Pattern

Access tokens should be short-lived for security. Refresh tokens allow getting new access tokens without re-login:

Refresh Token Implementation
const ACCESS_TOKEN_EXPIRES = '15m';   // Short-lived access token
const REFRESH_TOKEN_EXPIRES = '7d';  // Long-lived refresh token
const REFRESH_SECRET = 'different-secret-for-refresh';

// Generate both tokens
function generateTokens(user) {
  // Access token - short expiry, used for API requests
  const accessToken = jwt.sign(
    { id: user._id, email: user.email, role: user.role },
    JWT_SECRET,
    { expiresIn: ACCESS_TOKEN_EXPIRES }
  );

  // Refresh token - long expiry, only used to get new access tokens
  const refreshToken = jwt.sign(
    { id: user._id },
    REFRESH_SECRET,
    { expiresIn: REFRESH_TOKEN_EXPIRES }
  );

  return { accessToken, refreshToken };
}

// Login endpoint - returns both tokens
app.post('/api/auth/login', async (req, res) => {
  // ... validation logic ...
  const { accessToken, refreshToken } = generateTokens(user);

  // Set refresh token as HTTP-only cookie (more secure)
  res.cookie('refreshToken', refreshToken, {
    httpOnly: true,        // Not accessible via JavaScript
    secure: true,          // Only sent over HTTPS
    sameSite: 'strict',   // CSRF protection
    maxAge: 7 * 24 * 60 * 60 * 1000  // 7 days in ms
  });

  res.json({ accessToken, user: { id: user._id, email: user.email } });
});

// Refresh endpoint - get new access token
app.post('/api/auth/refresh', (req, res) => {
  // Get refresh token from cookie
  const refreshToken = req.cookies.refreshToken;

  if (!refreshToken) {
    return res.status(401).json({ error: 'Refresh token required' });
  }

  try {
    // Verify refresh token
    const decoded = jwt.verify(refreshToken, REFRESH_SECRET);

    // Generate new access token
    const accessToken = jwt.sign(
      { id: decoded.id },
      JWT_SECRET,
      { expiresIn: ACCESS_TOKEN_EXPIRES }
    );

    res.json({ accessToken });
  } catch (err) {
    res.status(401).json({ error: 'Invalid refresh token' });
  }
});

// Logout - clear refresh token
app.post('/api/auth/logout', (req, res) => {
  res.clearCookie('refreshToken');
  res.json({ message: 'Logged out successfully' });
});

Video Resources

Knowledge Check

1. What is the purpose of the JWT signature?

ATo encrypt the payload data
BTo verify the token hasn't been tampered with
CTo store additional user data
DTo compress the token size

2. Where should access tokens be sent in API requests?

AIn the request body
BAs a URL query parameter
CIn the Authorization header as "Bearer [token]"
DIn a cookie named "token"

3. Why should access tokens have short expiration times?

ATo limit damage if a token is stolen
BTo reduce server memory usage
CTo make tokens smaller
DTo improve encryption strength

Congratulations on completing Days 6-10!

You've learned MongoDB, Mongoose, data modeling, authentication with bcrypt, and JWT implementation. You now have the skills to build secure, database-backed APIs. Continue to Days 11-15 to learn error handling, file uploads, testing, and deployment!