Implementing Authentication in Next.js with JWT

Implementing Authentication in Next.js with JWT

Secure your Next.js application by implementing JWT-based authentication for seamless user login and registration processes.

Introduction

Authentication is a critical part of any web application. For modern, stateless applications, JSON Web Tokens (JWT) are a popular solution for handling authentication. JWT provides a secure and efficient way to pass authentication data between a client and a server.

Next.js, with its server-side rendering and API routes capabilities, allows us to easily integrate JWT for authentication. In this blog, we will explore how to set up JWT authentication in a Next.js app. We’ll cover everything from setting up the backend for issuing tokens to using them for protected routes on the frontend.


Main Content

1. Understanding JWT and Its Benefits

Before diving into the implementation, let’s briefly understand JWT and why it is used.

JWT (JSON Web Token) is a compact, URL-safe means of representing claims to be transferred between two parties. It consists of three parts:

  • Header: Contains metadata about the token, such as its type and signing algorithm.

  • Payload: Contains the claims or data (e.g., user information or permissions).

  • Signature: Used to verify the integrity of the token.

JWT provides several advantages:

  • Stateless Authentication: No need for server-side sessions, as the token itself holds the user’s data.

  • Security: Tokens are cryptographically signed, making them tamper-resistant.

  • Scalability: Because JWTs are stateless, they can easily be scaled across distributed systems.

2. Setting Up the Project

Let’s begin by setting up a Next.js project that supports JWT authentication. We’ll create a simple authentication system with login and registration functionality.

Step 1: Create a Next.js application

If you haven’t already created a Next.js app, you can do so with:

npx create-next-app@latest nextjs-jwt-auth
cd nextjs-jwt-auth

Step 2: Install the required dependencies

We’ll need a few dependencies like jsonwebtoken for creating and verifying tokens and bcryptjs for hashing passwords.

npm install jsonwebtoken bcryptjs
  • jsonwebtoken: A library used to generate and verify JWT tokens.

  • bcryptjs: A library for hashing passwords securely before saving them in the database.

3. Implementing Authentication Backend (API Routes)

In Next.js, API routes are a great way to implement server-side logic. We will create two endpoints for login and registration.

  1. Registration Endpoint:

Let’s first create a register.js file in the pages/api/auth folder:

// pages/api/auth/register.js

import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';

const users = []; // For demo purposes, a simple in-memory array to store users

export default async function handler(req, res) {
  if (req.method === 'POST') {
    const { username, password } = req.body;

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

    // Store the user data (here, we are simulating a database with an in-memory array)
    const newUser = { username, password: hashedPassword };
    users.push(newUser);

    // Create a JWT token
    const token = jwt.sign({ username }, 'secretKey', { expiresIn: '1h' });

    res.status(200).json({ token });
  } else {
    res.status(405).json({ message: 'Method Not Allowed' });
  }
}

Explanation:

  • Password Hashing: We use bcryptjs to hash the password before storing it in the database for security.

  • JWT Creation: After a user registers, a JWT is created with jsonwebtoken and signed with a secret key. This token will be used to authenticate the user in future requests.

  1. Login Endpoint:

Next, we will create a login.js endpoint to authenticate users:

// pages/api/auth/login.js

import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';

const users = [{ username: 'user', password: '$2a$10$UmpdYg41CthOpBvvVwYAu.LcX30OQHlTbZftjmFdffJ3LC5Gj4Vhi' }]; // Dummy user data

export default async function handler(req, res) {
  if (req.method === 'POST') {
    const { username, password } = req.body;

    // Find user by username
    const user = users.find(u => u.username === username);

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

    // Compare the password with the hashed password
    const isMatch = await bcrypt.compare(password, user.password);

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

    // Create a JWT token
    const token = jwt.sign({ username: user.username }, 'secretKey', { expiresIn: '1h' });

    res.status(200).json({ token });
  } else {
    res.status(405).json({ message: 'Method Not Allowed' });
  }
}

Explanation:

  • Password Comparison: We compare the entered password with the hashed password stored in the database using bcrypt.compare.

  • Token Creation: If authentication is successful, a JWT is issued, which the user can use for subsequent requests.

4. Protecting Routes with JWT

To protect sensitive routes, you need to verify the JWT token on the server-side. For example, let’s protect an API route called profile.js.

// pages/api/profile.js

import jwt from 'jsonwebtoken';

export default function handler(req, res) {
  if (req.method === 'GET') {
    const token = req.headers.authorization?.split(' ')[1]; // Extract token from Authorization header

    if (!token) {
      return res.status(401).json({ message: 'No token provided' });
    }

    try {
      // Verify the token
      const decoded = jwt.verify(token, 'secretKey');
      res.status(200).json({ message: 'Access granted', user: decoded });
    } catch (err) {
      res.status(401).json({ message: 'Invalid or expired token' });
    }
  } else {
    res.status(405).json({ message: 'Method Not Allowed' });
  }
}

Explanation:

  • Token Verification: We extract the token from the Authorization header and verify it using jwt.verify. If the token is valid, the user’s data is returned.

5. Using JWT on the Client Side

To use JWT on the client side, we need to store the token securely (e.g., in localStorage or cookies) and include it in requests to protected routes.

// Example of client-side request

const loginUser = async (username, password) => {
  const res = await fetch('/api/auth/login', {
    method: 'POST',
    body: JSON.stringify({ username, password }),
    headers: {
      'Content-Type': 'application/json',
    },
  });
  const data = await res.json();
  if (data.token) {
    localStorage.setItem('jwtToken', data.token);
  }
};

Explanation:

  • We store the JWT token in localStorage and send it in the Authorization header for subsequent requests to protected routes.

Examples/Case Studies

  1. Case Study: E-Commerce Site
    In an e-commerce application, JWT authentication can secure payment processing and account settings, ensuring that only authenticated users can access their cart and order history.

  2. Case Study: Admin Dashboard
    For an admin dashboard, JWT ensures that only authorized personnel can access sensitive administrative tools and user data, preventing unauthorized access to the backend.


Tips/Best Practices

  • Use HTTPS: Always serve JWT tokens over HTTPS to prevent man-in-the-middle attacks.

  • Token Expiry: Set a reasonable expiration time for JWT tokens to limit exposure if the token is compromised.

  • Store Tokens Securely: Store JWT tokens in httpOnly cookies or secure storage to reduce the risk of cross-site scripting (XSS) attacks.

  • Use Refresh Tokens: To avoid logging users out after the token expires, use refresh tokens that allow users to get a new JWT without re-authenticating.


Conclusion

JWT-based authentication is a robust and scalable solution for managing user sessions in modern web applications. By implementing JWT in your Next.js project, you can ensure that your users’ sessions are secure, while also benefiting from the stateless nature of JWT tokens.

With the steps outlined in this blog, you now have a full understanding of how to implement authentication using JWT in Next.js. Whether you are building a small app or a large-scale enterprise application, JWT is an excellent choice for managing authentication and user authorization.

Ready to integrate JWT authentication into your Next.js application? Start implementing it today to secure your users' data and improve the user experience. For further guidance, check out the official JWT documentation and explore best practices for secure authentication.


References/Resources