🌐 Detecting your location…
📢 Advertisement — Configure AdSense in Appearance → Customize → AdSense Settings

How to Implement JWT Authentication in Node.js / Express (Full Example 2026)

⏱️4 min read  ·  785 words

Jwt Authentication Nodejs Express

How to Implement JWT Authentication in Node.js / Express (Full Example 2026)

Question: How do I implement JWT (JSON Web Token) authentication in a Node.js/Express app with protected routes?

Setup

npm install express jsonwebtoken bcryptjs dotenv
npm install --save-dev nodemon
# .env
PORT=5000
JWT_SECRET=your-super-secret-key-min-32-chars-long-here
JWT_EXPIRES_IN=15m
JWT_REFRESH_EXPIRES_IN=7d

Project Structure

src/
├── middleware/
│   └── auth.js        # JWT verification middleware
├── routes/
│   ├── auth.js        # Login/register routes
│   └── protected.js   # Protected routes
├── utils/
│   └── jwt.js         # JWT helper functions
└── server.js

JWT Utilities

// src/utils/jwt.js
const jwt = require('jsonwebtoken');

const generateAccessToken = (userId) => {
  return jwt.sign(
    { userId, type: 'access' },
    process.env.JWT_SECRET,
    { expiresIn: process.env.JWT_EXPIRES_IN }
  );
};

const generateRefreshToken = (userId) => {
  return jwt.sign(
    { userId, type: 'refresh' },
    process.env.JWT_SECRET,
    { expiresIn: process.env.JWT_REFRESH_EXPIRES_IN }
  );
};

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

module.exports = { generateAccessToken, generateRefreshToken, verifyToken };

Auth Middleware

// src/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];

  try {
    const decoded = verifyToken(token);

    if (decoded.type !== 'access') {
      return res.status(401).json({ error: 'Invalid token type' });
    }

    req.userId = decoded.userId;
    next();
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Token expired', code: 'TOKEN_EXPIRED' });
    }
    return res.status(401).json({ error: 'Invalid token' });
  }
};

module.exports = { authenticate };

Auth Routes (Login + Register)

// src/routes/auth.js
const express = require('express');
const bcrypt = require('bcryptjs');
const { generateAccessToken, generateRefreshToken, verifyToken } = require('../utils/jwt');
const router = express.Router();

// In-memory user store (replace with database)
const users = [];

// Register
router.post('/register', async (req, res) => {
  const { email, password } = req.body;

  if (!email || !password) {
    return res.status(400).json({ error: 'Email and password required' });
  }

  const existingUser = users.find(u => u.email === email);
  if (existingUser) {
    return res.status(409).json({ error: 'User already exists' });
  }

  const hashedPassword = await bcrypt.hash(password, 12);
  const user = { id: Date.now().toString(), email, password: hashedPassword };
  users.push(user);

  const accessToken = generateAccessToken(user.id);
  const refreshToken = generateRefreshToken(user.id);

  res.status(201).json({
    message: 'User created',
    accessToken,
    refreshToken
  });
});

// Login
router.post('/login', async (req, res) => {
  const { email, password } = req.body;

  const user = users.find(u => u.email === email);
  if (!user) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  const isPasswordValid = await bcrypt.compare(password, user.password);
  if (!isPasswordValid) {
    return res.status(401).json({ error: 'Invalid credentials' });
  }

  const accessToken = generateAccessToken(user.id);
  const refreshToken = generateRefreshToken(user.id);

  res.json({ accessToken, refreshToken });
});

// Refresh token
router.post('/refresh', (req, res) => {
  const { refreshToken } = req.body;

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

  try {
    const decoded = verifyToken(refreshToken);

    if (decoded.type !== 'refresh') {
      return res.status(401).json({ error: 'Invalid token type' });
    }

    const newAccessToken = generateAccessToken(decoded.userId);
    res.json({ accessToken: newAccessToken });
  } catch (error) {
    res.status(401).json({ error: 'Invalid or expired refresh token' });
  }
});

module.exports = router;

Protected Routes

// src/routes/protected.js
const express = require('express');
const { authenticate } = require('../middleware/auth');
const router = express.Router();

// All routes in this file require authentication
router.use(authenticate);

router.get('/profile', (req, res) => {
  res.json({
    message: 'Protected route accessed',
    userId: req.userId
  });
});

router.get('/dashboard', (req, res) => {
  res.json({ data: 'Dashboard data', userId: req.userId });
});

module.exports = router;

Server Setup

// src/server.js
require('dotenv').config();
const express = require('express');
const authRoutes = require('./routes/auth');
const protectedRoutes = require('./routes/protected');

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

app.use('/auth', authRoutes);
app.use('/api', protectedRoutes);

app.listen(process.env.PORT, () => {
  console.log(`Server running on port ${process.env.PORT}`);
});

Testing the API

# Register
curl -X POST http://localhost:5000/auth/register   -H "Content-Type: application/json"   -d '{"email":"user@example.com","password":"SecurePass123!"}'

# Login - get tokens
curl -X POST http://localhost:5000/auth/login   -H "Content-Type: application/json"   -d '{"email":"user@example.com","password":"SecurePass123!"}'

# Access protected route
curl http://localhost:5000/api/profile   -H "Authorization: Bearer YOUR_ACCESS_TOKEN_HERE"

# Refresh expired token
curl -X POST http://localhost:5000/auth/refresh   -H "Content-Type: application/json"   -d '{"refreshToken":"YOUR_REFRESH_TOKEN_HERE"}'

Security Best Practices

  • Short-lived access tokens — 15 minutes is standard. Reduces exposure window.
  • Long-lived refresh tokens — 7-30 days. Store securely (httpOnly cookie preferred).
  • Strong JWT secret — Minimum 32 characters, randomly generated
  • Never store tokens in localStorage for sensitive apps — Use httpOnly cookies for refresh tokens
  • Implement refresh token rotation — Issue new refresh token on each refresh
  • Maintain a token blacklist for logout — Redis-based in production

httpOnly Cookie Pattern (More Secure)

// Store refresh token in httpOnly cookie instead of response body
res.cookie('refreshToken', refreshToken, {
  httpOnly: true,   // JS can't read this
  secure: true,     // HTTPS only
  sameSite: 'strict',
  maxAge: 7 * 24 * 60 * 60 * 1000  // 7 days in ms
});

res.json({ accessToken }); // Only return access token in body

✍️ Leave a Comment

Your email address will not be published. Required fields are marked *

🌐 Read in:🇬🇧 English🇩🇪 Deutsch🇧🇷 Português🇸🇦 العربية🇮🇳 हिन्दी🇧🇩 বাংলা