Advertisement

JWT Authentication: Complete Guide for Developers in 2025

October 26, 202530 min readSecurity

JSON Web Tokens (JWT) have become the industry standard for authentication in modern web applications. This comprehensive guide covers everything from basic concepts to advanced security practices.

What is JWT?

JWT (JSON Web Token) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed.

JWT Structure

A JWT consists of three parts separated by dots (.):

xxxxx.yyyyy.zzzzz

Header.Payload.Signature

1. Header

The header typically consists of two parts: the type of token (JWT) and the signing algorithm (HMAC, RSA, etc.).

{
  "alg": "HS256",
  "typ": "JWT"
}

2. Payload

The payload contains the claims - statements about the user and additional data.

{
  "sub": "1234567890",
  "name": "John Doe",
  "email": "john@example.com",
  "iat": 1516239022,
  "exp": 1516242622
}

3. Signature

The signature is created by encoding the header and payload, then signing them with a secret key.

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

How JWT Authentication Works

Authentication Flow:

  1. 1. User logs in with credentials (username/password)
  2. 2. Server validates credentials and generates JWT
  3. 3. Server sends JWT back to client
  4. 4. Client stores JWT (localStorage, sessionStorage, or cookie)
  5. 5. Client includes JWT in Authorization header for subsequent requests
  6. 6. Server verifies JWT signature and extracts user information
  7. 7. Server processes request if JWT is valid

Implementing JWT Authentication

Backend Implementation (Node.js + Express)

Install required packages:

npm install jsonwebtoken bcrypt express

Create authentication routes:

const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const express = require('express');
const router = express.Router();

const JWT_SECRET = process.env.JWT_SECRET;
const JWT_EXPIRES_IN = '7d';

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

    // Validate input
    if (!email || !password || !name) {
      return res.status(400).json({
        error: 'All fields are required'
      });
    }

    // Check if user exists
    const existingUser = await User.findOne({ email });
    if (existingUser) {
      return res.status(400).json({
        error: 'User already exists'
      });
    }

    // Hash password
    const hashedPassword = await bcrypt.hash(password, 10);

    // Create user
    const user = await User.create({
      email,
      password: hashedPassword,
      name
    });

    // Generate JWT
    const token = jwt.sign(
      {
        userId: user._id,
        email: user.email
      },
      JWT_SECRET,
      { expiresIn: JWT_EXPIRES_IN }
    );

    res.status(201).json({
      message: 'User created successfully',
      token,
      user: {
        id: user._id,
        email: user.email,
        name: user.name
      }
    });

  } catch (error) {
    res.status(500).json({
      error: 'Registration failed'
    });
  }
});

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

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

    // Find user
    const user = await User.findOne({ email });
    if (!user) {
      return res.status(401).json({
        error: 'Invalid credentials'
      });
    }

    // Verify password
    const validPassword = await bcrypt.compare(
      password,
      user.password
    );

    if (!validPassword) {
      return res.status(401).json({
        error: 'Invalid credentials'
      });
    }

    // Generate JWT
    const token = jwt.sign(
      {
        userId: user._id,
        email: user.email
      },
      JWT_SECRET,
      { expiresIn: JWT_EXPIRES_IN }
    );

    res.json({
      message: 'Login successful',
      token,
      user: {
        id: user._id,
        email: user.email,
        name: user.name
      }
    });

  } catch (error) {
    res.status(500).json({
      error: 'Login failed'
    });
  }
});

module.exports = router;

Creating Authentication Middleware

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

const authMiddleware = async (req, res, next) => {
  try {
    // Get token from header
    const authHeader = req.headers.authorization;

    if (!authHeader || !authHeader.startsWith('Bearer ')) {
      return res.status(401).json({
        error: 'No token provided'
      });
    }

    const token = authHeader.split(' ')[1];

    // Verify token
    const decoded = jwt.verify(token, process.env.JWT_SECRET);

    // Add user info to request
    req.user = {
      userId: decoded.userId,
      email: decoded.email
    };

    next();

  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      return res.status(401).json({
        error: 'Token expired'
      });
    }

    if (error.name === 'JsonWebTokenError') {
      return res.status(401).json({
        error: 'Invalid token'
      });
    }

    res.status(500).json({
      error: 'Authentication failed'
    });
  }
};

module.exports = authMiddleware;

Using the Middleware

const authMiddleware = require('./middleware/auth');

// Protected route example
router.get('/profile', authMiddleware, async (req, res) => {
  try {
    const user = await User.findById(req.user.userId)
      .select('-password');

    res.json({ user });
  } catch (error) {
    res.status(500).json({ error: 'Failed to fetch profile' });
  }
});

// Update profile
router.put('/profile', authMiddleware, async (req, res) => {
  try {
    const { name } = req.body;

    const user = await User.findByIdAndUpdate(
      req.user.userId,
      { name },
      { new: true }
    ).select('-password');

    res.json({
      message: 'Profile updated',
      user
    });
  } catch (error) {
    res.status(500).json({ error: 'Update failed' });
  }
});

Frontend Implementation (React)

Login Component

import { useState } from 'react';
import axios from 'axios';

function Login() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [error, setError] = useState('');

  const handleLogin = async (e) => {
    e.preventDefault();
    setError('');

    try {
      const response = await axios.post('/api/auth/login', {
        email,
        password
      });

      // Store token
      localStorage.setItem('token', response.data.token);

      // Store user info
      localStorage.setItem('user', JSON.stringify(response.data.user));

      // Redirect to dashboard
      window.location.href = '/dashboard';

    } catch (err) {
      setError(err.response?.data?.error || 'Login failed');
    }
  };

  return (
    <form onSubmit={handleLogin}>
      {error && <div className="error">{error}</div>}

      <input
        type="email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="Email"
        required
      />

      <input
        type="password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
        placeholder="Password"
        required
      />

      <button type="submit">Login</button>
    </form>
  );
}

Axios Interceptor for Authentication

// utils/axios.js
import axios from 'axios';

const api = axios.create({
  baseURL: process.env.REACT_APP_API_URL
});

// Request interceptor - add token to headers
api.interceptors.request.use(
  (config) => {
    const token = localStorage.getItem('token');

    if (token) {
      config.headers.Authorization = `Bearer ${token}`;
    }

    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// Response interceptor - handle token expiration
api.interceptors.response.use(
  (response) => response,
  (error) => {
    if (error.response?.status === 401) {
      // Token expired or invalid
      localStorage.removeItem('token');
      localStorage.removeItem('user');
      window.location.href = '/login';
    }

    return Promise.reject(error);
  }
);

export default api;

Security Best Practices

1. Use Strong Secret Keys

// Generate a strong secret
const crypto = require('crypto');
const secret = crypto.randomBytes(64).toString('hex');

// Store in environment variables
// .env file
JWT_SECRET=your-super-secret-key-here-min-32-chars

2. Set Appropriate Expiration Times

// Short-lived access tokens
const accessToken = jwt.sign(payload, JWT_SECRET, {
  expiresIn: '15m'
});

// Long-lived refresh tokens
const refreshToken = jwt.sign(payload, REFRESH_SECRET, {
  expiresIn: '7d'
});

3. Implement Refresh Token Strategy

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

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

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

    // Check if refresh token is blacklisted
    const blacklisted = await RefreshToken.findOne({
      token: refreshToken,
      revoked: true
    });

    if (blacklisted) {
      return res.status(401).json({
        error: 'Token revoked'
      });
    }

    // Generate new access token
    const newAccessToken = jwt.sign(
      { userId: decoded.userId, email: decoded.email },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );

    res.json({
      accessToken: newAccessToken
    });

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

Common Security Vulnerabilities

1. Storing Sensitive Data in JWT

❌ Never store sensitive data in JWT:

  • Passwords
  • Credit card numbers
  • Social security numbers
  • Private keys

2. Not Validating Token Expiration

// Always verify expiration
try {
  const decoded = jwt.verify(token, JWT_SECRET);

  // Additional check (belt and suspenders)
  if (decoded.exp < Date.now() / 1000) {
    throw new Error('Token expired');
  }

} catch (error) {
  // Handle expired or invalid token
}

Conclusion

JWT authentication is a powerful tool for securing modern applications. By following these best practices and implementing proper security measures, you can build robust authentication systems that protect your users and data.

Try Our JWT Decoder Tool

Decode and inspect JWT tokens instantly with our free online tool!

Try JWT Decoder →
Advertisement