CORS

This guide covers Cross-Origin Resource Sharing (CORS) in AdonisJS applications. You will learn how to:

  • Install and configure the CORS middleware
  • Control which origins, methods, and headers are allowed
  • Handle credentials in cross-origin requests
  • Debug common CORS errors

Overview

When a browser makes a request to a different domain than the one serving the current page, it enforces Cross-Origin Resource Sharing (CORS) restrictions. This security mechanism prevents malicious scripts from making unauthorized requests to your API on behalf of users.

For example, if your frontend runs on app.example.com and your API runs on api.example.com, the browser will block requests from the frontend unless your API explicitly allows that origin. The same applies during local development when your frontend runs on localhost:3000 and your API on localhost:3333.

Before making certain cross-origin requests, browsers send a preflight request using the OPTIONS HTTP method. This preflight asks your server which origins, methods, and headers are permitted. Your server must respond with the appropriate CORS headers, and only then will the browser proceed with the actual request.

AdonisJS handles CORS through the @adonisjs/cors package, which provides middleware that automatically responds to preflight requests and attaches the correct headers to all responses.

Installation

Install and configure the package using the following command:

node ace add @adonisjs/cors
See steps performed by the add command
  1. Installs the @adonisjs/cors package using the detected package manager.

  2. Registers the following service provider inside the adonisrc.ts file.

    adonisrc.ts
    {
      providers: [
        // ...other providers
        () => import('@adonisjs/cors/cors_provider')
      ]
    }
  3. Creates the config/cors.ts file. This file contains the configuration settings for CORS.

  4. Registers the following middleware inside the start/kernel.ts file.

    start/kernel.ts
    server.use([
      () => import('@adonisjs/cors/cors_middleware')
    ])

Configuration

The CORS configuration lives in config/cors.ts. Here is the default configuration with all available options:

config/cors.ts
import { defineConfig } from '@adonisjs/cors'

const corsConfig = defineConfig({
  enabled: true,
  origin: true,
  methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'],
  headers: true,
  exposeHeaders: [],
  credentials: true,
  maxAge: 90,
})

export default corsConfig

Enabling and disabling CORS

The enabled option turns the middleware on or off without removing it from the middleware stack. This is useful when you want to disable CORS temporarily during debugging or in specific environments.

config/cors.ts
{
  enabled: process.env.NODE_ENV !== 'test'
}

Configuring allowed origins

The origin option controls which domains can make cross-origin requests to your API. This sets the Access-Control-Allow-Origin response header.

To allow all origins dynamically (the response header will mirror the requesting origin):

config/cors.ts
{
  origin: true
}

To disallow all cross-origin requests:

config/cors.ts
{
  origin: false
}

To allow specific domains, provide an array of origins:

config/cors.ts
{
  origin: ['https://app.example.com', 'https://admin.example.com']
}

To allow any origin using the wildcard:

config/cors.ts
{
  origin: '*'
}
Warning

When credentials is set to true, the wildcard * cannot be used as the Access-Control-Allow-Origin header value. Browsers reject this combination for security reasons. AdonisJS automatically handles this by reflecting the requesting origin instead of sending the literal * when both origin: '*' and credentials: true are configured.

For dynamic origin validation, provide a callback function. This is useful when allowed origins are stored in a database or when you need custom validation logic:

config/cors.ts
{
  origin: (requestOrigin, ctx) => {
    /**
     * requestOrigin is the value of the Origin header.
     * Return true to allow, false to deny.
     */
    const allowedOrigins = ['https://app.example.com']
    return allowedOrigins.includes(requestOrigin)
  }
}

Configuring allowed methods

The methods option specifies which HTTP methods are permitted for cross-origin requests. The browser's preflight request includes an Access-Control-Request-Method header, and the server checks this value against the allowed methods.

config/cors.ts
{
  methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE']
}

Configuring allowed headers

The headers option controls which request headers are permitted in cross-origin requests. The browser's preflight request includes an Access-Control-Request-Headers header listing the headers the client wants to send.

To allow all headers:

config/cors.ts
{
  headers: true
}

To allow specific headers:

config/cors.ts
{
  headers: ['Content-Type', 'Accept', 'Authorization']
}

For dynamic header validation, provide a callback:

config/cors.ts
{
  headers: (requestHeaders, ctx) => {
    return true
  }
}

Exposing response headers

By default, browsers only expose a limited set of response headers to JavaScript. The exposeHeaders option lets you specify additional headers that should be accessible to the client.

config/cors.ts
{
  exposeHeaders: ['X-Request-Id', 'X-RateLimit-Remaining']
}

Allowing credentials

The credentials option controls whether cookies, authorization headers, and TLS client certificates can be included in cross-origin requests. When enabled, the server sends the Access-Control-Allow-Credentials: true header.

config/cors.ts
{
  credentials: true
}
Tip

Enable credentials when your frontend needs to send authentication cookies or the Authorization header to your API. Without this, browsers strip credentials from cross-origin requests.

Caching preflight responses

The maxAge option specifies how long (in seconds) browsers should cache preflight responses. This reduces the number of preflight requests for repeated cross-origin calls.

config/cors.ts
{
  maxAge: 90
}

Setting maxAge to null omits the Access-Control-Max-Age header entirely. Setting it to -1 sends the header but disables caching.

Common scenarios

API serving a single-page application

When your API and frontend are deployed on different domains, configure CORS to allow your frontend's origin with credentials:

config/cors.ts
import { defineConfig } from '@adonisjs/cors'

const corsConfig = defineConfig({
  enabled: true,
  origin: ['https://app.example.com'],
  methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'],
  headers: true,
  credentials: true,
  maxAge: 90,
})

export default corsConfig

Local development with different ports

During development, your frontend and backend often run on different ports. Configure CORS to allow your local frontend origin:

config/cors.ts
import { defineConfig } from '@adonisjs/cors'

const corsConfig = defineConfig({
  enabled: true,
  origin: (requestOrigin) => {
    /**
     * Allow localhost on any port during development.
     */
    return requestOrigin.startsWith('http://localhost')
  },
  methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'],
  headers: true,
  credentials: true,
  maxAge: 90,
})

export default corsConfig

Public API with no credentials

If your API is public and does not require cookies or authentication headers, you can use a permissive configuration:

config/cors.ts
import { defineConfig } from '@adonisjs/cors'

const corsConfig = defineConfig({
  enabled: true,
  origin: '*',
  methods: ['GET', 'HEAD', 'POST'],
  headers: true,
  credentials: false,
  maxAge: 86400,
})

export default corsConfig

See also