How to Secure Your Next.js API Routes with Middleware

React Specialist
November 25, 2024
Updated on February 17, 2025
0 MIN READ
#microservices#next-js#secure#your

Introduction

Next.js has become a popular framework for building full-stack applications, offering both frontend and backend capabilities through API routes. However, exposing API routes without proper security measures can lead to vulnerabilities such as unauthorized access, data breaches, and injection attacks.

Middleware in Next.js provides a powerful way to intercept and secure API requests before they reach their destination. In this guide, we’ll explore how to implement middleware to enhance the security of your Next.js API routes, covering authentication, rate limiting, and request validation.

Understanding Next.js Middleware

Next.js middleware runs before a request is completed, allowing you to modify the response, redirect requests, or block them entirely. Middleware is particularly useful for securing API routes because it can enforce security policies consistently across all endpoints.

Middleware files are placed in the /middleware.js (or /middleware.ts) file at the root of your project or inside specific directories to apply middleware selectively.

Here’s a basic example of a middleware that logs requests:

// middleware.js import { NextResponse } from 'next/server'; export function middleware(request) { console.log(`Request received: ${request.method} ${request.url}`); return NextResponse.next(); }

This middleware logs every request but allows it to proceed (NextResponse.next()). Now, let’s explore how to use middleware for security.

Securing API Routes with Authentication

One of the most common security measures is ensuring that only authenticated users can access certain API routes. You can use middleware to verify authentication tokens (e.g., JWT) before processing the request.

Example: JWT Authentication Middleware

// middleware.js import { NextResponse } from 'next/server'; import { verify } from 'jsonwebtoken'; export async function middleware(request) { const { pathname } = request.nextUrl; // Skip middleware for public routes if (pathname.startsWith('/api/public')) { return NextResponse.next(); } // Check for auth token in headers const authToken = request.headers.get('authorization')?.split(' ')[1]; if (!authToken) { return new Response('Unauthorized', { status: 401 }); } try { // Verify the token (replace 'your-secret-key' with your actual secret) verify(authToken, 'your-secret-key'); return NextResponse.next(); } catch (error) { return new Response('Invalid token', { status: 403 }); } }

This middleware checks for a JWT in the Authorization header and rejects unauthorized requests. You can customize the logic further, such as validating user roles or integrating with third-party auth providers like Auth0 or Firebase.

Implementing Rate Limiting

Rate limiting prevents abuse by restricting the number of requests a client can make within a given time frame. This is crucial for protecting your API from brute-force attacks and excessive traffic.

Example: Rate Limiting with Redis

For a scalable solution, we’ll use Redis to track request counts. First, install the required packages:

npm install ioredis

Then, implement the middleware:

// middleware.js import { NextResponse } from 'next/server'; import Redis from 'ioredis'; const redis = new Redis(process.env.REDIS_URL); export async function middleware(request) { const ip = request.ip || request.headers.get('x-forwarded-for'); const key = `rate-limit:${ip}`; // Increment request count const currentCount = await redis.incr(key); // Set expiration if this is the first request if (currentCount === 1) { await redis.expire(key, 60); // Reset every 60 seconds } // Block if rate limit exceeded if (currentCount > 100) { return new Response('Rate limit exceeded', { status: 429 }); } return NextResponse.next(); }

This middleware allows up to 100 requests per minute per IP address. Adjust the limits based on your application’s needs.

Validating Request Inputs

Another critical security measure is validating incoming request data to prevent injection attacks (e.g., SQL injection, XSS). Middleware can sanitize and validate payloads before they reach your API logic.

Example: Request Validation with Zod

Zod is a TypeScript-first schema validation library. Install it with:

npm install zod

Here’s how to validate a JSON request body:

// middleware.js import { NextResponse } from 'next/server'; import { z } from 'zod'; const userSchema = z.object({ email: z.string().email(), password: z.string().min(8), }); export async function middleware(request) { if (request.method === 'POST' && request.nextUrl.pathname.startsWith('/api/users')) { try { const body = await request.json(); userSchema.parse(body); // Throws if validation fails return NextResponse.next(); } catch (error) { return new Response('Invalid input', { status: 400 }); } } return NextResponse.next(); }

This ensures that any POST request to /api/users includes a valid email and password.

Conclusion

Securing your Next.js API routes with middleware is an efficient way to enforce authentication, rate limiting, and input validation across your application. By intercepting requests before they reach your endpoints, you reduce the risk of common security vulnerabilities while keeping your code DRY and maintainable.

To recap:

  • Use middleware to verify JWT tokens for authentication.
  • Implement rate limiting to prevent abuse.
  • Validate request payloads to block malicious input.

For advanced use cases, consider integrating additional security measures like CORS policies, CSRF protection, and logging. By adopting these practices, you’ll build a more robust and secure Next.js application.

Would you like help implementing any of these techniques in your project? Let us know in the comments!

Share this article