JWT

JWT Authentication: Implementation Guide

Learn how to implement secure JWT authentication from scratch. This comprehensive guide covers backend setup, token generation, validation, and best practices for production applications.

What is JWT Authentication?

JWT (JSON Web Token) authentication is a stateless authentication mechanism where the server generates a signed token after verifying user credentials. The client stores this token and sends it with subsequent requests to prove authentication.

Why JWT?

  • Stateless - no server-side session storage needed
  • Scalable - works seamlessly with load balancers
  • Cross-domain - perfect for microservices and APIs
  • Mobile-friendly - easy token storage in mobile apps

JWT Authentication Flow

User Login
Server Validates Credentials
Generate JWT Token
Client Stores Token
Send Token with Requests
Server Validates Token
1. POST /api/login { username, password }
2. Server: Verify credentials against database
3. Server: Generate signed JWT with user info
4. Response: { "token": "eyJhbGciOiJIUzI1NiIs..." }
5. Client: Store in localStorage/cookie
6. GET /api/user Authorization: Bearer <token>
7. Server: Verify signature + expiration
8. Response: Protected resource data

Backend Implementation (Node.js)

1. Install Dependencies

npm install jsonwebtoken bcrypt express

2. Generate JWT Secret

// Use a strong random secret (256-bit)
const crypto = require('crypto');
const secret = crypto.randomBytes(32).toString('hex');
// Store in .env file: JWT_SECRET=your_secret_here

3. User Login Endpoint

const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
app.post('/api/login', async (req, res) => {
  const { username, password } = req.body;
  
  // Find user in database
  const user = await User.findOne({ username });
  if (!user) return res.status(401).json({ error: 'Invalid credentials' });
  
  // Verify password
  const valid = await bcrypt.compare(password, user.passwordHash);
  if (!valid) return res.status(401).json({ error: 'Invalid credentials' });
  
  // Generate JWT
  const token = jwt.sign(
    { userId: user.id, username: user.username, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: '1h', issuer: 'your-app' }
  );
  
  res.json({ token });
});

4. Authentication Middleware

function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
  
  if (!token) return res.status(401).json({ error: 'Token required' });
  
  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) return res.status(403).json({ error: 'Invalid token' });
    req.user = user; // Attach user info to request
    next();
  });
}
// Use in routes
app.get('/api/profile', authenticateToken, (req, res) => {
  res.json({ user: req.user });
});

Frontend Implementation (JavaScript)

Login Function

async function login(username, password) {
  const response = await fetch('/api/login', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password })
  });
  
  if (!response.ok) throw new Error('Login failed');
  
  const { token } = await response.json();
  localStorage.setItem('token', token);
  return token;
}

Authenticated API Calls

async function fetchProtectedData() {
  const token = localStorage.getItem('token');
  
  const response = await fetch('/api/profile', {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  });
  
  if (response.status === 401) {
    // Token expired or invalid - redirect to login
    localStorage.removeItem('token');
    window.location.href = '/login';
    return;
  }
  
  return await response.json();
}

Refresh Token Pattern

For better security, implement refresh tokens alongside short-lived access tokens:

Token Type Lifetime Storage Purpose
Access Token 15 min - 1 hour Memory/localStorage API authentication
Refresh Token 7-30 days httpOnly cookie Get new access tokens
// Login returns both tokens
app.post('/api/login', async (req, res) => {
  // ... validate credentials ...
  
  const accessToken = jwt.sign(payload, SECRET, { expiresIn: '15m' });
  const refreshToken = jwt.sign(payload, REFRESH_SECRET, { expiresIn: '7d' });
  
  // Store refresh token in database
  await RefreshToken.create({ userId: user.id, token: refreshToken });
  
  res.cookie('refreshToken', refreshToken, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 7 * 24 * 60 * 60 * 1000
  });
  
  res.json({ accessToken });
});
// Refresh endpoint
app.post('/api/refresh', async (req, res) => {
  const { refreshToken } = req.cookies;
  if (!refreshToken) return res.status(401).json({ error: 'No refresh token' });
  
  // Verify refresh token
  const decoded = jwt.verify(refreshToken, REFRESH_SECRET);
  
  // Check if token exists in database
  const exists = await RefreshToken.findOne({ token: refreshToken });
  if (!exists) return res.status(403).json({ error: 'Invalid refresh token' });
  
  // Generate new access token
  const accessToken = jwt.sign({ userId: decoded.userId }, SECRET, { expiresIn: '15m' });
  res.json({ accessToken });
});

Security Best Practices

Critical Security Rules:

  • Use HTTPS only - tokens transmitted over plain HTTP can be intercepted
  • Strong secret: Minimum 256-bit random secret
  • Short expiration: 15 min to 1 hour for access tokens
  • Validate everything: Signature, expiration, issuer, audience
  • HttpOnly cookies: For refresh tokens to prevent XSS
  • Never log tokens: Tokens are sensitive credentials

Common Pitfalls to Avoid

  • Storing sensitive data in JWT: JWTs are Base64-encoded, not encrypted. Don't put passwords or credit cards in tokens.
  • No expiration time: Always set exp claim. Tokens without expiration never expire.
  • Weak secrets: Don't use "secret", "password", or short strings.
  • Client-side validation only: Always verify tokens on the server; client checks are for UX only.
  • Not handling expired tokens: Implement refresh token logic or redirect to login.

Testing Your Implementation

# Test login
curl -X POST http://localhost:3000/api/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"password123"}'
# Test protected route
curl http://localhost:3000/api/profile \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6..."

Try Our Tools

Debug and test your JWT tokens:

Related Articles

Base64

What is Base64 Encoding and How Does it Work?

Learn everything about Base64 encoding: what it is, how it works, when to use it, and practical examples for developers.

Base64

Base64 vs Binary: Understanding the Difference

Deep dive into the differences between Base64 and Binary encoding. Learn which format to use for your specific use case.

Base64

How to Embed Images in HTML Using Base64

Complete guide to embedding images directly in HTML using Base64 data URIs. Includes performance tips and best practices.