DEV Community

Cover image for How to configure CORS for Next.js
Marek Elmayan for Theodo

Posted on

How to configure CORS for Next.js

Hey ๐Ÿ‘‹,

I've been working on a new package called next-armored that helps you secure your Next.js application. My first release is out, and I'm excited to share it with you. The first tool I added is a middleware to help you configure CORS for your Next.js server.

If you're only interested in how to use it, and how to set it up, you can directly go check the ๐Ÿ‘‰ tutorial section ๐Ÿ‘ˆ.

Otherwise, let's start with a quick reminder of what Cross-Origin Resource Sharing (CORS) is and when you need to set it up in your Next.js app.

Drop a star on Github โญ๏ธ

Check on NPM ๐Ÿ“ฆ


๐Ÿ”’ I - What is the CORS policy ?

Cross-Origin Resource Sharing a web browser security feature

CORS is a security feature that allows you to control which origins are allowed to access your resources. It is enforced by web browsers through Access-control-allow-* headers.

CORS protects users of your app from malicious sites trying to perform operations (like retrieving personal information or account deletion) on your server without their knowledge. While the request will still reach your server, if it is correctly configured, it will attach CORS security headers based on the requestโ€™s origin.

  • If the request comes from your allowed origins, the browser will allow access to the serverโ€™s response.

  • If the request originates from a disallowed or malicious site, the browser will block access to the response.

For example, in the simplest case of a GET request, an attacker could try to retrieve information. For other methods, such as DELETE, the browser sends a preflight request first. Based on the server's CORS configuration, the browser decides whether to proceed with the request.

CORS explained with a schema

For more in-depth information, check this article by a friend of mine or the MDN Documentation.
For insights into CORS vulnerabilities, explore Outpost24โ€™s blog.

โ“ What is an origin, and how do you differentiate them?

An origin is defined as a combination of the protocol, host, and port of a resource. For example, the origin of https://example.com/index.html is https://example.com. A change in any of these three components results in a different origin.

Origin explained with a schema

โšก๏ธ Subdomains change the host, so https://example.com and https://api.example.com are considered different origins.


๐Ÿ› ๏ธ II - When should you configure CORS for your Next.js application?

We saw that https://example.com and https://api.example.com are different origins. So if your server is at https://api.example.com but your frontend app at https://example.com, you need to configure CORS on your server to allow requests from your frontend.

Why is that? Historically, browsers enforce the Same Origin Policy (SOP), allowing requests only between the same origin for security. This policyโ€™s limitations necessitated the introduction of CORS to enable controlled cross-origin requests, such as inter-operability between different subdomains or public APIs.

Now, what about Next.js ? If you use Next.js 13 or later with the app router (e.g., a folder like ./src/app/api), your frontend (e.g., https://your-domain.com) and API (e.g., https://your-domain.com/api) share the same origin. Here, the SOP suffices, and enabling CORS without a valid reason could introduce vulnerabilities.

So, when should you enable CORS?

  1. If your API is accessed by another frontend (e.g., https://admin.your-domain.com).
  2. If your API is public, allowing access from any origin (ensure the API doesnโ€™t handle sensitive data).

๐Ÿ‘จ๐Ÿผโ€๐Ÿ’ป III - How to configure CORS for Next.js with next-armored

๐Ÿ“ฆ Install the next-armored package

Select your favorite package manager (e.g. pnpm) and install the next-armored package by running:

pnpm add next-armored
Enter fullscreen mode Exit fullscreen mode

๐Ÿฅ‡ Use the CORS middleware as your first middleware

Create or update your ./src/middleware.ts:

import { NextRequest } from "next/server";
import { createCorsMiddleware } from "next-armored/cors";

const corsMiddleware = createCorsMiddleware({
  origins: ["https://example.com", "http://localhost:5173"],
});

export function middleware(request: NextRequest) {
  return corsMiddleware(request);
}

export const config = {
  matcher: ["/api/:path*"],
};
Enter fullscreen mode Exit fullscreen mode

In Next.js, the middleware is executed before every request matching the matcher pattern inside the config object.
For this example, I will suppose that you just created the middleware file. So you just need to match all your api routes.
If you are using the app router, you can match all your api routes by using the '/api/:path*' pattern.
Then import the createCorsMiddleware function from next-armored/cors and pass your configuration object to it. This will allow you to build the cors middleware with the origins you want to allow and pass it to the middleware function.

๐ŸŽผ Compose your CORS middleware with other middleware

If you already have middleware, compose them as follows:

import { NextRequest, NextResponse } from "next/server";
import { createCorsMiddleware } from "next-armored/cors";

const corsMiddleware = createCorsMiddleware({
  origins: ["https://example.com", "http://localhost:5173"],
});

const otherMiddleware = (request: NextRequest) => {
  console.log("otherMiddleware");
  return NextResponse.next();
};

export function middleware(request: NextRequest) {
  const response = otherMiddleware(request);
  const isApi = request.nextUrl.pathname.startsWith("/api");
  if (isApi) {
    return corsMiddleware(request, response);
  }
  return response;
}

export const config = {
  matcher: ["/api/:path*", "/home/:path*"],
};
Enter fullscreen mode Exit fullscreen mode

If your matcher didn't previously include the api routes, you will have to add it to the matcher.
Then you can call the other middleware first and get the response.
Since the cors middleware should be applied only to the api routes, you have to check if the request is an api request and then apply the cors middleware only in that case. This can be done easily by checking the pathname of the request with request.nextUrl.pathname.startsWith('/api').

Finally, pass the response to the cors middleware as the 2nd argument. In this case, the cors middleware will attach the headers to the existing response instead of returning a new response.


Alternatively, use a utility to chain middleware. Here is a snippet of how you can do it but it shall be adapted to the need of each.

export type CustomMiddleware = (
  request: NextRequest,
  event: NextFetchEvent,
  response: NextResponse
) => NextMiddlewareResult;

type MiddlewareFactory = (next: CustomMiddleware) => CustomMiddleware;

export const chainMiddlewares = (
  functions: MiddlewareFactory[],
  index = 0
): CustomMiddleware => {
  const current = functions[index];

  if (current) {
    const next = chainMiddlewares(functions, index + 1);

    return current(next);
  }

  return (
    _request: NextRequest,
    _event: NextFetchEvent,
    response: NextResponse
  ) => response;
};
Enter fullscreen mode Exit fullscreen mode
export const middleware = chainMiddleware([
  middleware1,
  middleware2,
  corsMiddleware,
]);
Enter fullscreen mode Exit fullscreen mode

โš™๏ธ Full CORS middleware configuration

const corsMiddleware = createCorsMiddleware({
  origins: ["https://example.com", "http://localhost:5173"],
  methods: ["GET", "POST"],
  headers: ["Content-Type"],
  maxAge: 60 * 60 * 24,
  optionsSuccessStatus: 204,
  allowCredentials: true,
  exposedHeaders: ["Content-Type"],
  preflightContinue: false,
});
Enter fullscreen mode Exit fullscreen mode

You have to at least specify the origins option because keeping * by default is not recommended as it can be a security risk. If you are building a public api, you can allow all origins by passing origins: ['*'] but you should be aware of the security implications.

You can define other options as well:

  • origins - array of origins that are allowed to access the resource. The only one to be required.
  • methods - array of methods that are allowed to access the resource. Default value to ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"].
  • headers - array of headers that are allowed to access the resource. Default value to ["Content-Type", "Authorization"].
  • maxAge - number of seconds that the results of a preflight request can be cached. Default value to 60 * 60 * 24 (1 day).
  • optionsSuccessStatus - status code to send for successful OPTIONS requests. Default value to 204.
  • allowCredentials - boolean value to indicate if credentials are allowed to be sent with the request. Default value to true.
  • exposedHeaders - array of headers that are exposed to the client. Default value to [].
  • preflightContinue - boolean value to indicate if it should pass the CORS preflight response to the next handler. Default value to false.

๐Ÿšซ Enable/disable CORS for specific paths

Enable CORS for specific paths:

const corsMiddleware = createCorsMiddleware(
  { origins: ["https://example.com", "http://localhost:5173"] },
  {
    includes: [{ startsWith: "/api/v2", additionalIncludes: ["example"] }],
  }
);
Enter fullscreen mode Exit fullscreen mode

This will enable CORS for all paths that start with /api/v2 and at the same time include example in the pathname.
api/v2/product/example will have CORS enabled, but api/v1/product/example/test will not.

Disable CORS for specific paths:

const corsMiddleware = createCorsMiddleware(
  { origins: ["https://example.com", "http://localhost:5173"] },
  {
    excludes: [{ startsWith: "/api/restricted" }],
  }
);
Enter fullscreen mode Exit fullscreen mode

In this case, all the routes starting with /api/restricted will not enforce CORS but the SOP (Same-Origin Policy) as the default behavior suggests.


๐Ÿ”ฎ IV - Why use next-armored?

  • ๐Ÿš€ Easy to use: Create the middleware with few lines.

  • ๐Ÿ”ง Flexible: Customize middleware configurations to fit your needs.

  • ๐Ÿ›ก๏ธ Secure: Default configurations are based on best practices and security standards.

  • โœ… Type-safe: Middleware is fully typed and compatible with Next.js and TypeScript. Also it's tree-shakable.

  • ๐ŸŒ Open source: Audit, report issues, and contribute.


๐Ÿ”Ž V - Next steps for next-armored

I started with a cors middleware for Next.js but my goal is to create a bunch of utils to help you secure your Next.js application. So I definitely plan to add more utils and more middleware to this package.
The next one will probably be a middleware to handle Content Security Policy (CSP) headers. But if you have any suggestion, or specific needs, please let me know. ๐Ÿ˜Š

Drop a star on Github โญ๏ธ

Check on NPM ๐Ÿ“ฆ

Top comments (0)