Loading

Quipoin Menu

Learn • Practice • Grow

express-js / JWT Authentication in Express
tutorial

JWT Authentication in Express

While session-based authentication works well for traditional web apps, modern APIs often use **JWT (JSON Web Tokens)** for authentication. JWTs are stateless, scalable, and perfect for single-page applications and mobile apps.

What is JWT?

JWT (pronounced "jot") is an open standard for securely transmitting information between parties as a JSON object. It's digitally signed, so it can be verified and trusted. A JWT consists of three parts:
  • Header: Contains metadata about the token (type, signing algorithm).
  • Payload: Contains the claims (user data, expiration, etc.).
  • Signature: Verifies the token hasn't been tampered with.

Think of JWT as a digitally signed passport. The passport contains your information and is stamped by an authority. Anyone can read it, but only the authority can verify it's authentic.

JWT Authentication Flow
  1. User logs in with credentials.
  2. Server verifies credentials and generates a JWT.
  3. Server sends JWT to client (usually in response body or cookie).
  4. Client stores JWT (localStorage, sessionStorage, or cookie).
  5. Client includes JWT in Authorization header for subsequent requests.
  6. Server verifies JWT and grants access.

Installation
npm install jsonwebtoken bcrypt

Complete JWT Authentication Example

1. User Model (models/User.js)
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');

const userSchema = new mongoose.Schema({
  username: { type: String, required: true, unique: true },
  email: { type: String, required: true, unique: true },
  password: { type: String, required: true },
  role: { type: String, enum: ['user', 'admin'], default: 'user' }
}, { timestamps: true });

<!-- 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();
});

<!-- Method to compare passwords -->
userSchema.methods.comparePassword = async function(candidatePassword) {
  return await bcrypt.compare(candidatePassword, this.password);
};

module.exports = mongoose.model('User', userSchema);

2. JWT Utilities (utils/jwt.js)
const jwt = require('jsonwebtoken');

const generateToken = (user) => {
  return jwt.sign(
    { id: user._id, username: user.username, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: process.env.JWT_EXPIRES_IN || '7d' }
  );
};

const verifyToken = (token) => {
  try {
    return jwt.verify(token, process.env.JWT_SECRET);
  } catch (err) {
    return null;
  }
};

module.exports = { generateToken, verifyToken };

3. Auth Middleware (middleware/auth.js)
const { verifyToken } = require('../utils/jwt');

const 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];
  const decoded = verifyToken(token);
 
  if (!decoded) {
    return res.status(401).json({ error: 'Invalid or expired token' });
  }
 
  req.user = decoded;
  next();
};

const authorize = (...roles) => {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: 'Not authenticated' });
    }
   
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    next();
  };
};

module.exports = { authenticate, authorize };

4. Auth Routes (routes/auth.js)
const express = require('express');
const User = require('../models/User');
const { generateToken } = require('../utils/jwt');
const { authenticate } = require('../middleware/auth');
const router = express.Router();

<!-- Register -->
router.post('/register', async (req, res) => {
  try {
    const { username, email, password } = req.body;
   
    <!-- Check if user exists -->
    const existingUser = await User.findOne({ $or: [{ username }, { email }] });
    if (existingUser) {
      return res.status(400).json({ error: 'Username or email already exists' });
    }
   
    <!-- Create user -->
    const user = await User.create({ username, email, password });
   
    <!-- Generate token -->
    const token = generateToken(user);
   
    res.status(201).json({
      message: 'User created successfully',
      token,
      user: { id: user._id, username, email, role: user.role }
    });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

<!-- Login -->
router.post('/login', async (req, res) => {
  try {
    const { username, password } = req.body;
   
    <!-- Find user -->
    const user = await User.findOne({ username });
   
    if (!user) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }
   
    <!-- Check password -->
    const isMatch = await user.comparePassword(password);
   
    if (!isMatch) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }
   
    <!-- Generate token -->
    const token = generateToken(user);
   
    res.json({
      message: 'Login successful',
      token,
      user: { id: user._id, username: user.username, role: user.role }
    });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

<!-- Get current user (protected) -->
router.get('/me', authenticate, (req, res) => {
  res.json({ user: req.user });
});

module.exports = router;

5. Protected Routes Example
const { authenticate, authorize } = require('../middleware/auth');

<!-- Any authenticated user -->
router.get('/profile', authenticate, (req, res) => {
  res.json({ message: 'Profile data', user: req.user });
});

<!-- Admin only -->
router.get('/admin', authenticate, authorize('admin'), (req, res) => {
  res.json({ message: 'Admin dashboard' });
});

Environment Variables (.env)
JWT_SECRET=your-super-secret-jwt-key-change-this-in-production
JWT_EXPIRES_IN=7d

Two Minute Drill

  • JWT is a stateless authentication method – perfect for APIs and SPAs.
  • Tokens consist of Header, Payload, and Signature.
  • Store JWT secret securely in environment variables.
  • Client sends token in Authorization header: `Bearer `.
  • Use middleware to authenticate and authorize requests.

Need more clarification?

Drop us an email at career@quipoinfotech.com