Developer Guides

Implementing JWT Authentication in Node.js

Learn how to set up JWT authentication in Node.js and secure your API endpoints with step-by-step guidance.

5 min read

JSON Web Tokens (JWT) are widely used for implementing authentication and authorization in modern applications. They enable secure, stateless communication between parties by embedding user claims in a compact, URL-safe token. This guide details how to implement JWT authentication in a Node.js application using Express.

Overview of JWT Authentication

JSON Web Tokens (JWTs) are a secure way of transmitting information between parties. They offer the following benefits:

  • Compact and URL-Safe: Easy to send through query strings, POST parameters, or HTTP headers.
  • Stateless Authentication: Eliminates the need for server-side session storage.
  • Integrity Verification: Cryptographic signatures ensure token integrity.

JWTs contain three parts: a header, a payload, and a signature. The payload contains claims like user identification, while the signature ensures the token's authenticity.

Prerequisites for Implementing JWT in Node.js

Before you start, ensure the following prerequisites are met:

prerequisites

  • Node.js (v14 or later) installed on your machine.
  • Familiarity with JavaScript and REST APIs.
  • A code editor like Visual Studio Code.
  • Optionally, install the REST Client extension or use Postman for API testing.

Setting Up the Project

Follow these steps to initialize your Node.js application:

steps

  1. Create a new project and initialize it:

    bash
    mkdir jwt-auth && cd jwt-auth
    npm init -y
  2. Install required dependencies:

    bash
    npm install express jsonwebtoken dotenv
    npm install --save-dev nodemon
  3. Update package.json to add a development script:

    json
    {
      "scripts": {
        "start": "node server.js",
        "dev": "nodemon server.js"
      }
    }

Creating a Basic Express Server

Set up a simple Express server and test its functionality.

steps

  1. Create a server.js file:

    bash
    touch server.js
  2. Add the following code to initialize the server (using CommonJS syntax for compatibility):

    javascript
    const express = require('express');
    
    const app = express();
    
    app.use(express.json());
    
    // Basic route
    app.get('/posts', (req, res) => {
      res.json([
        { username: 'John', title: 'Post 1' },
        { username: 'Jane', title: 'Post 2' }
      ]);
    });
    
    app.listen(3000, () => console.log("Server running on port 3000"));
  3. Start the server:

    bash
    npm run dev
  4. Verify your server at http://localhost:3000/posts to confirm you receive JSON data.

Integrating JWT for Authentication

To protect your API endpoints, integrate JWT to issue tokens and authenticate users.

steps

  1. Update server.js to include jsonwebtoken and dotenv requires and configure dotenv:

    javascript
    const jwt = require('jsonwebtoken');
    const dotenv = require('dotenv');
    dotenv.config();
  2. Add a .env file to store your secret keys:

    bash
    echo "ACCESS_TOKEN_SECRET=your_random_access_token_secret_key" > .env
    echo "REFRESH_TOKEN_SECRET=your_random_refresh_token_secret_key" >> .env
  3. Implement a /login route to issue JWT tokens:

    javascript
    app.post('/login', (req, res) => {
      const { username } = req.body;
      const user = { name: username };
    
      const accessToken = jwt.sign(user, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '15m' });
      res.json({ accessToken });
    });
  4. Create middleware to verify tokens:

    javascript
    function authenticateToken(req, res, next) {
      const authHeader = req.headers['authorization'];
      const token = authHeader && authHeader.split(' ')[1];
      if (!token) return res.sendStatus(401);
    
      jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, user) => {
        if (err) return res.sendStatus(403);
        req.user = user;
        next();
      });
    }
  5. Protect the /posts endpoint:

    javascript
    app.get('/posts', authenticateToken, (req, res) => {
      const posts = [
        { username: 'John', title: 'Post 1' },
        { username: 'Jane', title: 'Post 2' }
      ];
      res.json(posts.filter(post => post.username === req.user.name));
    });

Adding Refresh Token Functionality

Enhance security further by introducing refresh tokens.

steps

  1. Create an in-memory store for refresh tokens:

    javascript
    let refreshTokens = [];
  2. Update /login to issue both access and refresh tokens:

    javascript
    app.post('/login', (req, res) => {
      const { username } = req.body;
      const user = { name: username };
    
      const accessToken = jwt.sign(user, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '15m' });
      const refreshToken = jwt.sign(user, process.env.REFRESH_TOKEN_SECRET);
      refreshTokens.push(refreshToken);
    
      res.json({ accessToken, refreshToken });
    });
  3. Add an endpoint to refresh tokens:

    javascript
    app.post('/token', (req, res) => {
      const { token } = req.body;
      if (!token || !refreshTokens.includes(token)) return res.sendStatus(403);
    
      jwt.verify(token, process.env.REFRESH_TOKEN_SECRET, (err, user) => {
        if (err) return res.sendStatus(403);
    
        const accessToken = jwt.sign({ name: user.name }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '15m' });
        res.json({ accessToken });
      });
    });
  4. Securely manage refresh tokens by adding a /logout route:

    javascript
    app.delete('/logout', (req, res) => {
      refreshTokens = refreshTokens.filter(token => token !== req.body.token);
      res.sendStatus(204);
    });

Security Best Practices for JWT

Final Steps: Testing and Deployment

Once your JWT-based authentication is set up, test the API endpoints thoroughly using tools like Postman or the REST Client extension. Before deploying, ensure the following:


For further clarity, here are answers to some common questions:

FAQ

How do you store refresh tokens securely?

While it's possible to store access tokens in memory or cookies, refresh tokens should be stored using secure httpOnly cookies or in a server-side database to reduce the risk of token theft.

How do you revoke a specific user's access tokens?

The refresh token mechanism allows the server to maintain a list of valid tokens. To revoke access, remove the refresh token from the active token list. The next request to refresh an access token will fail.

What's the main difference between access tokens and refresh tokens?

Access tokens are short-lived, allowing users to authorize actions temporarily without repeated credential checks. Refresh tokens are long-lived and are used to issue new access tokens securely.