FuntranslatorCreate Fun Language Translations
Free
Back to Blog
July 8, 202510 min read

How to Use JWT for Authentication (Node.js Practical Guide)

A comprehensive hands-on tutorial for implementing JWT authentication in Node.js using Express and jsonwebtoken. Learn to build secure login systems with token-based authentication.

In this comprehensive tutorial, you'll learn how to implement JWT authentication in a Node.js application using the jsonwebtoken package and Express framework. We'll build a complete authentication system with login functionality and protected routes, demonstrating real-world JWT implementation patterns.

πŸš€ 1. Environment Setup

Before we start building our JWT authentication system, let's set up the necessary dependencies and project structure.

Installing Required Packages

First, initialize your Node.js project and install the essential packages:

npm init -y
npm install express jsonwebtoken dotenv bcryptjs
npm install --save-dev nodemon

Here's what each package does:

  • express: Web framework for Node.js
  • jsonwebtoken: Library for working with JSON Web Tokens
  • dotenv: Loads environment variables from .env file
  • bcryptjs: Library for hashing passwords
  • nodemon: Development tool that automatically restarts the server

Project Structure

Create the following project structure:

jwt-auth-demo/
β”œβ”€β”€ .env
β”œβ”€β”€ package.json
β”œβ”€β”€ server.js
β”œβ”€β”€ middleware/
β”‚   └── auth.js
└── routes/
    β”œβ”€β”€ auth.js
    └── protected.js

Environment Configuration

Create a .env file in your project root with a strong JWT secret:

# .env
JWT_SECRET=your_super_secure_jwt_secret_key_here
PORT=3000
NODE_ENV=development

Important: Use a strong, randomly generated secret key in production. You can use our JWTSecrets generator to create a secure key.

πŸ‘€ 2. Login Interface (Token Issuance)

Let's create the core authentication logic starting with the login endpoint that issues JWT tokens.

Basic Server Setup

First, set up the basic Express server in server.js:

// server.js
require('dotenv').config();
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(express.json());

// Mock user database (in production, use a real database)
const users = [
  {
    id: 1,
    username: 'demo',
    email: 'demo@example.com',
    // Password: 'password123' (hashed)
    password: '$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi'
  }
];

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

Login Endpoint Implementation

Now, let's implement the login endpoint that validates credentials and issues JWT tokens:

// Add this to server.js
app.post('/api/login', async (req, res) => {
  try {
    const { username, password } = req.body;
    
    // Validate input
    if (!username || !password) {
      return res.status(400).json({ 
        error: 'Username and password are required' 
      });
    }
    
    // Find user in database
    const user = users.find(u => u.username === username);
    if (!user) {
      return res.status(401).json({ 
        error: 'Invalid credentials' 
      });
    }
    
    // Verify password
    const isValidPassword = await bcrypt.compare(password, user.password);
    if (!isValidPassword) {
      return res.status(401).json({ 
        error: 'Invalid credentials' 
      });
    }
    
    // Create JWT payload (don't include sensitive data)
    const payload = {
      id: user.id,
      username: user.username,
      email: user.email
    };
    
    // Sign the token
    const token = jwt.sign(
      payload,
      process.env.JWT_SECRET,
      { 
        expiresIn: '1h',
        issuer: 'jwt-auth-demo',
        audience: 'jwt-auth-demo-users'
      }
    );
    
    // Return token and user info
    res.json({
      message: 'Login successful',
      token,
      user: {
        id: user.id,
        username: user.username,
        email: user.email
      }
    });
    
  } catch (error) {
    console.error('Login error:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

Token Structure Explanation

The JWT token we create contains:

  • Payload: User information (id, username, email)
  • Expiration: 1 hour from issuance
  • Issuer: Identifies who issued the token
  • Audience: Identifies who the token is intended for

πŸ›‘οΈ 3. Protected Routes (Token Verification)

Now let's create middleware to protect routes by verifying JWT tokens.

Authentication Middleware

Create middleware/auth.js:

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

function authMiddleware(req, res, next) {
  try {
    // Get token from Authorization header
    const authHeader = req.headers.authorization;
    
    if (!authHeader) {
      return res.status(401).json({ 
        error: 'Access denied. No token provided.' 
      });
    }
    
    // Extract token from "Bearer TOKEN" format
    const token = authHeader.split(' ')[1];
    
    if (!token) {
      return res.status(401).json({ 
        error: 'Access denied. Invalid token format.' 
      });
    }
    
    // Verify token
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    
    // Add user info to request object
    req.user = decoded;
    
    // Continue to next middleware/route handler
    next();
    
  } catch (error) {
    if (error.name === 'TokenExpiredError') {
      return res.status(401).json({ 
        error: 'Token expired. Please login again.' 
      });
    }
    
    if (error.name === 'JsonWebTokenError') {
      return res.status(403).json({ 
        error: 'Invalid token.' 
      });
    }
    
    console.error('Auth middleware error:', error);
    res.status(500).json({ 
      error: 'Internal server error' 
    });
  }
}

module.exports = authMiddleware;

Advanced Middleware with Role-Based Access

For more complex applications, you might need role-based access control:

// middleware/auth.js (extended version)
function requireRole(roles) {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ 
        error: 'Authentication required' 
      });
    }
    
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ 
        error: 'Insufficient permissions' 
      });
    }
    
    next();
  };
}

module.exports = { authMiddleware, requireRole };

πŸ“‘ 4. Testing the Authentication System

Let's create protected routes and test our authentication system.

Protected Routes

Add these protected routes to your server.js:

// Add this to server.js
const authMiddleware = require('./middleware/auth');

// Protected route - User profile
app.get('/api/profile', authMiddleware, (req, res) => {
  res.json({
    message: `Hello, ${req.user.username}!`,
    user: req.user,
    timestamp: new Date().toISOString()
  });
});

// Protected route - Dashboard
app.get('/api/dashboard', authMiddleware, (req, res) => {
  res.json({
    message: 'Welcome to your dashboard',
    user: {
      id: req.user.id,
      username: req.user.username,
      email: req.user.email
    },
    data: {
      lastLogin: new Date().toISOString(),
      permissions: ['read', 'write'],
      preferences: {
        theme: 'dark',
        language: 'en'
      }
    }
  });
});

// Token refresh endpoint
app.post('/api/refresh', authMiddleware, (req, res) => {
  try {
    // Create new token with extended expiration
    const newToken = jwt.sign(
      {
        id: req.user.id,
        username: req.user.username,
        email: req.user.email
      },
      process.env.JWT_SECRET,
      { expiresIn: '1h' }
    );
    
    res.json({
      message: 'Token refreshed successfully',
      token: newToken
    });
  } catch (error) {
    res.status(500).json({ error: 'Failed to refresh token' });
  }
});

Testing with cURL

Here are some cURL commands to test your authentication system:

# 1. Login to get a token
curl -X POST http://localhost:3000/api/login \
  -H "Content-Type: application/json" \
  -d '{"username": "demo", "password": "password123"}'

# 2. Access protected route (replace TOKEN with actual token)
curl -X GET http://localhost:3000/api/profile \
  -H "Authorization: Bearer YOUR_TOKEN_HERE"

# 3. Test without token (should fail)
curl -X GET http://localhost:3000/api/profile

Frontend Integration Example

Here's how you might integrate this with a frontend application:

// Frontend JavaScript example
class AuthService {
  constructor() {
    this.baseURL = 'http://localhost:3000/api';
    this.token = localStorage.getItem('jwt_token');
  }
  
  async login(username, password) {
    try {
      const response = await fetch(`${this.baseURL}/login`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ username, password })
      });
      
      const data = await response.json();
      
      if (response.ok) {
        this.token = data.token;
        localStorage.setItem('jwt_token', this.token);
        return data;
      } else {
        throw new Error(data.error);
      }
    } catch (error) {
      console.error('Login failed:', error);
      throw error;
    }
  }
  
  async getProfile() {
    try {
      const response = await fetch(`${this.baseURL}/profile`, {
        headers: {
          'Authorization': `Bearer ${this.token}`
        }
      });
      
      if (response.ok) {
        return await response.json();
      } else {
        throw new Error('Failed to fetch profile');
      }
    } catch (error) {
      console.error('Profile fetch failed:', error);
      throw error;
    }
  }
  
  logout() {
    this.token = null;
    localStorage.removeItem('jwt_token');
  }
}

// Usage
const auth = new AuthService();

// Login
auth.login('demo', 'password123')
  .then(result => console.log('Login successful:', result))
  .catch(error => console.error('Login failed:', error));

// Get profile
auth.getProfile()
  .then(profile => console.log('Profile:', profile))
  .catch(error => console.error('Profile fetch failed:', error));

πŸ”’ Security Best Practices

When implementing JWT authentication, follow these security best practices:

Token Security

  • Use HTTPS: Always transmit tokens over encrypted connections
  • Short expiration times: Use short-lived tokens (15-60 minutes)
  • Secure storage: Store tokens securely on the client side
  • Token rotation: Implement refresh token mechanisms

Secret Management

  • Strong secrets: Use cryptographically strong secret keys
  • Environment variables: Never hardcode secrets in your code
  • Key rotation: Regularly rotate your JWT secrets
  • Multiple environments: Use different secrets for dev/staging/production

Input Validation

  • Validate all inputs: Always validate and sanitize user inputs
  • Rate limiting: Implement rate limiting on authentication endpoints
  • Password policies: Enforce strong password requirements
  • Account lockout: Implement account lockout after failed attempts

πŸš€ Next Steps

Now that you have a basic JWT authentication system, consider these enhancements:

  • Database integration: Replace the mock user array with a real database
  • Refresh tokens: Implement refresh token functionality
  • Password reset: Add password reset functionality
  • Email verification: Implement email verification for new accounts
  • Two-factor authentication: Add 2FA for enhanced security
  • Logging and monitoring: Add comprehensive logging and monitoring

Conclusion

You've successfully implemented a complete JWT authentication system in Node.js! This tutorial covered the essential components: token generation, verification middleware, and protected routes. The system provides a solid foundation for building secure, scalable authentication in your applications.

Remember that security is an ongoing process. Regularly review and update your authentication implementation, stay informed about security best practices, and consider using established authentication services for production applications.

For more advanced JWT topics, check out our related articles on JWT fundamentals and building unbreakable JWT security.

Related Articles

What is JWT? From Concept to Structure Explained

A comprehensive guide to understanding JWT (JSON Web Token) - from basic concepts to detailed structure analysis. Learn how JWT works and why it's essential for modern authentication.

Read Article

JWTSecrets Generator: Building an Unbreakable JWT Security Line

Learn how to create an impenetrable security barrier for your applications using properly generated JWT secrets and best implementation practices.

Read Article