Next.js Middleware: How to Use It for Authentication & Security

Published: 12 September 2023

Introduction

Next.js provides powerful features that allow developers to build scalable, high-performance web applications. One such feature is middleware, which enables developers to run custom code before a request is completed. Middleware is useful for a variety of tasks, including authentication, authorization, logging, and security.

In this post, we will explore how Next.js Middleware can be leveraged for handling authentication and securing your API routes. We’ll also dive into practical examples for creating secure applications that only allow authenticated users to access certain routes and resources.


What is Middleware in Next.js?

Middleware in Next.js refers to functions that run between the request and the response. These functions allow you to add logic that processes the incoming request before it reaches the actual handler or API route.

How Middleware Works in Next.js:

  1. Request Interception: Middleware can intercept incoming requests before they are processed by the route handler or static page.
  2. Custom Logic Execution: Middleware can run custom logic, such as checking for authentication, managing cookies, or even transforming the request.
  3. Response Handling: Based on the logic, middleware can modify the response or allow the request to continue to the next step.

In Next.js, middleware functions are executed before the page is rendered or before an API response is sent back to the client.


Securing API Routes with Middleware

One of the most common use cases for Next.js middleware is securing API routes to ensure that only authenticated users can access certain resources. Here, we’ll implement middleware that checks for a valid authentication token before allowing access to API routes.

Step 1: Set Up Firebase Authentication

For this example, we will use Firebase Authentication to handle user login and issue authentication tokens.

  1. Install Firebase in your Next.js project.
npm install firebase
  1. Initialize Firebase in a file (e.g., firebase.js) and add Firebase Authentication logic. For the purposes of this post, we assume you already have Firebase set up.
// lib/firebase.js
import firebase from 'firebase/app';
import 'firebase/auth';

const firebaseConfig = {
  apiKey: 'YOUR_API_KEY',
  authDomain: 'YOUR_AUTH_DOMAIN',
  projectId: 'YOUR_PROJECT_ID',
  storageBucket: 'YOUR_STORAGE_BUCKET',
  messagingSenderId: 'YOUR_MESSAGING_SENDER_ID',
  appId: 'YOUR_APP_ID',
};

// Initialize Firebase
if (!firebase.apps.length) {
  firebase.initializeApp(firebaseConfig);
} else {
  firebase.app();
}

const auth = firebase.auth();

export { auth };

Step 2: Creating Middleware for Authentication

Now, let’s create middleware to validate the authentication token in incoming requests.

  1. In your Next.js project, create a new file under middleware (e.g., middleware/auth.js).
// middleware/auth.js
import { NextResponse } from 'next/server';
import { auth } from '../lib/firebase';

// This function checks for the user authentication token
export async function middleware(req) {
  // Get the token from the request's authorization header
  const token = req.headers.get('Authorization');

  // If there's no token, return a 401 response
  if (!token) {
    return new NextResponse('Unauthorized', { status: 401 });
  }

  try {
    // Verify the token using Firebase Authentication
    const decodedToken = await auth.verifyIdToken(token.replace('Bearer ', ''));
    
    // Add the decoded token to the request to pass to the API handler
    req.user = decodedToken;

    // If authentication is successful, proceed to the next middleware or API route
    return NextResponse.next();
  } catch (error) {
    return new NextResponse('Unauthorized', { status: 401 });
  }
}

Explanation:

  • Extract Token: We retrieve the authentication token from the request’s Authorization header. This token is typically sent by the client when a user logs in and the client stores the token in a cookie or localStorage.
  • Token Verification: We use Firebase’s verifyIdToken() function to validate the token. This ensures that only requests with a valid Firebase token can proceed.
  • NextResponse: If the token is valid, we let the request continue to the API handler using NextResponse.next(). Otherwise, we return an unauthorized response with a 401 status.

Step 3: Apply Middleware to API Routes

To apply the middleware, you need to set it up in your API routes. Next.js provides the ability to easily use middleware with API routes.

  1. Create a new API route (e.g., pages/api/protected.js) that you want to protect with authentication.
// pages/api/protected.js
import { NextApiResponse } from 'next';

export default async function handler(req, res) {
  // Check if user is authenticated (middleware will add the user)
  if (!req.user) {
    return res.status(401).json({ message: 'Unauthorized' });
  }

  // Protected API route logic
  res.status(200).json({ message: 'This is a protected route', user: req.user });
}

This API route is now secured. If the request does not include a valid Firebase authentication token, it will receive a 401 Unauthorized response.


Step 4: Using the Protected API Route on the Frontend

On the frontend, you can fetch the protected data from the API route. For this example, let’s assume that the client is passing the token in the Authorization header.

  1. Client-Side Request:
// components/ProtectedPage.js
import { useEffect, useState } from 'react';

const ProtectedPage = () => {
  const [message, setMessage] = useState('');
  const [error, setError] = useState('');

  useEffect(() => {
    const fetchProtectedData = async () => {
      const token = localStorage.getItem('authToken'); // Get the token from localStorage or cookies

      const res = await fetch('/api/protected', {
        method: 'GET',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      });

      if (res.ok) {
        const data = await res.json();
        setMessage(data.message);
      } else {
        setError('You are not authorized to view this page.');
      }
    };

    fetchProtectedData();
  }, []);

  return (
    <div>
      {error && <p>{error}</p>}
      {message && <p>{message}</p>}
    </div>
  );
};

export default ProtectedPage;

In this example, we’re retrieving the authToken (which would be obtained after login) from the client’s localStorage or cookies and using it in the Authorization header when calling the API route.


Conclusion

Middleware in Next.js is a powerful tool for handling authentication and securing your API routes. By adding custom logic before your API routes are accessed, you can enforce security rules such as authentication checks to make sure that only authorized users can access specific resources.

In this guide, we’ve shown how to:

  • Set up Firebase Authentication to handle user login and token generation.
  • Create middleware that validates the token and secures API routes.
  • Secure API routes by checking the token before granting access.

Middleware in Next.js opens up many possibilities for enhancing security, improving performance, and adding more sophisticated logic to your application without complex setups. By implementing authentication middleware, you can easily control access to sensitive data and ensure only authorized users can interact with your API.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *