How to Set Up CORS for Your REST API

CORS errors are a common challenge when building APIs that interact with front-end apps on different domains. This guide explains what CORS is, why it matters, how to configure it across frameworks, and how to avoid the most common pitfalls.

2 minutes ago   •   14 min read

By Savan Kharod
Table of contents

Encountering CORS (Cross-Origin Resource Sharing) errors can be a common hurdle when developing RESTful APIs that interact with front-end applications hosted on different domains. These errors often manifest as messages like "No 'Access-Control-Allow-Origin' header is present on the requested resource," indicating that the browser's security policy is blocking the request.

CORS is a critical security feature implemented by browsers to prevent web applications from requesting a domain other than the one that served the web page. While this policy enhances security, it can pose challenges during the development and integration phases, especially when APIs need to be accessed across different origins.

In this comprehensive guide, we'll delve into the fundamentals of CORS and its role in web security, identify scenarios where CORS becomes relevant, provide step-by-step instructions to configure CORS in various server environments, including Node.js (Express), Python (Flask), and Java (Spring Boot), highlight common pitfalls to avoid during CORS configuration, and outline best practices to ensure secure and efficient cross-origin communications. 

So let’s get into it. 

💡
Ship better APIs with Treblle. Treblle gives you real-time visibility into every API request and response—so you can catch issues like CORS misconfigurations before they escalate.

What is CORS?

Cross-Origin Resource Sharing (CORS) is an HTTP-header based mechanism that enables servers to specify which origins, HTTP methods, and request headers are permitted when a browser makes a cross-origin request. 

It extends the browser’s Same-Origin Policy, which restricts how scripts loaded from one origin can interact with resources from a different origin. 

Key CORS concepts include origins (defined by protocol, host, and port); specific HTTP headers such as Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers that inform the browser which requests are allowed; preflight OPTIONS requests used by browsers to verify server permissions before sending certain requests; and explicitly allowed HTTP methods like GET, POST, PUT, and DELETE

When CORS is misconfigured, browsers will block the request and display an error such as “No ‘Access-Control-Allow-Origin’ header is present on the requested resource,” preventing legitimate cross-origin calls.

Example of a Typical CORS Error Message

When CORS is not configured correctly, browsers will block cross-origin requests and display an error in the developer console. A common error looks like:

Access to fetch at 'https://api.example.com/data' from origin 'https://frontend.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

This message indicates that the browser enforced the Same-Origin Policy because the server did not include the required CORS header granting access to the requesting origin. 

When Does CORS Matter?

Cross-Origin Resource Sharing (CORS) becomes relevant when a web application running in one origin (defined by the combination of protocol, domain, and port) attempts to request resources from a different origin. This scenario commonly arises in modern web development, especially in applications with decoupled front-end and back-end architectures.

Scenarios Triggering CORS

Browsers primarily enforce CORS to uphold the Same-Origin Policy, which restricts how scripts loaded from one origin can interact with resources from another origin. Common scenarios where CORS is triggered include:

  • Single Page Applications (SPAs): A front-end application served from https://app.example.com, making API calls to https://api.example.com.
  • Third-Party API Integrations: Integrating services like payment gateways or social media APIs that reside on different domains.
  • Content Delivery Networks (CDNs): Loading resources such as images, fonts, or scripts from a CDN hosted on a different domain.

In these cases, the browser enforces CORS policies to determine whether the cross-origin request is permitted. If the server hosting the resource does not include the appropriate CORS headers in its response, the browser blocks the request to protect the user.

Browser vs. Server Concerns

It's important to note that CORS is a browser-enforced security mechanism. Server-to-server communications, such as API requests made from a backend service or using tools like curl or Postman, are not subject to CORS restrictions. CORS exists to protect users from malicious websites that try to access resources on a different domain without proper permission.

For instance, if a malicious site tries to make an unauthorized request to a user's bank API, the browser's CORS policy will prevent the request from succeeding unless the bank's server explicitly allows it. However, if the same request is made directly from a server or a tool outside the browser context, CORS does not apply, and the server must implement its own security measures to handle such requests.

How to Configure CORS for Your REST API

In this section, we’ll walk through the steps to configure Cross-Origin Resource Sharing (CORS) for your REST API. 

Choose Your Server Technology

Before configuring CORS, it’s important to recognize that the exact setup depends on the language and framework your API uses. Different server technologies have distinct middleware or built-in support for CORS. 

For example, Node.js (Express) relies on the widely adopted cors middleware to inject appropriate headers into responses, whereas Python (Flask) uses the Flask-CORS extension to simplify configuration. Java (Spring Boot) offers declarative CORS settings via annotations or a global configuration class. 

Understanding which library or feature to use for your stack is the first step to a smooth CORS implementation.

Basic CORS Setup Examples

Below are step-by-step examples illustrating how to configure CORS in three popular server environments. Each example includes settings to restrict origins, allowed methods, headers, and, optionally credentials.

Node.js (Express)

1. Install the CORS Middleware
To begin, install the cors package via npm:

npm install cors

2. Import and Configure cors
In your main application file (e.g., app.js), import Express and the cors middleware:

const express = require('express');
const cors = require('cors');
const app = express();

3. Define CORS Options
Create a configuration object specifying which origin(s), methods, and headers are permitted:

const corsOptions = {
  origin: 'https://frontend.example.com',            // Only allow this origin
  methods: ['GET', 'POST', 'PUT', 'DELETE'],         // Allowed HTTP methods
  allowedHeaders: ['Content-Type', 'Authorization'], // Allowed request headers
  credentials: true                                  // Allow cookies or other credentials
};

4. Apply the Middleware
Enable CORS globally by passing the options to app.use():

app.use(cors(corsOptions));

5. Start the Server

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

With this configuration, only requests from https://frontend.example.com using the specified methods and headers, along with credentials, will be accepted. Any other cross-origin requests will be blocked by the browser.

Python (Flask)

1. Install Flask-CORS
Use pip to install the Flask-CORS extension:

pip install -U flask-cors

2. Import and Initialize
In your Flask application (e.g., app.py), import the extension and wrap your app:

from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

By default, CORS(app) enables CORS for all routes and origins. To restrict settings, provide a resources argument.

3. Restrict to Specific Origins and Paths
If you only want to allow certain origins (for example, https://frontend.example.com), configure as follows:

CORS(
    app,
    resources={r"/api/*": {"origins": "https://frontend.example.com"}},
    supports_credentials=True
)

4. Define an API Route

@app.route("/api/data", methods=["GET", "POST"])
def get_data():
    return {"message": "CORS configured in Flask"}

5. Run the Flask App

if __name__ == "__main__":
    app.run(port=5000)

This setup ensures that only requests matching /api/* from https://frontend.example.com are allowed, and cookies or other credentials are supported.

Java (Spring Boot)

1. Global CORS Configuration (Recommended)
Create a configuration class (e.g., WebConfig.java) under your package’s config directory:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/api/**")
                        .allowedOrigins("https://frontend.example.com")
                        .allowedMethods("GET", "POST", "PUT", "DELETE")
                        .allowedHeaders("Content-Type", "Authorization")
                        .allowCredentials(true);
            }
        };
    }
}

2. Controller-Level CORS (Alternative)
Alternatively, you can annotate specific controllers or endpoints:

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@CrossOrigin(
    origins = "https://frontend.example.com",
    methods = { RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE },
    allowedHeaders = "Content-Type",
    allowCredentials = "true"
)
public class MyController {
    @GetMapping("/api/data")
    public ResponseEntity<String> getData() {
        return ResponseEntity.ok("CORS configured in Spring Boot");
    }
}

3. Run the Spring Boot Application

Execute your main class (e.g., Application.java) to start the embedded server:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

With these settings, only requests to paths under /api/** from https://frontend.example.com using the specified methods and headers, plus credentials, are allowed.

All other cross-origin requests will be blocked by the browser.

Configuring Preflight Requests

When a browser detects that a cross-origin request is “non-simple” (for example, using methods other than GET, HEAD, POST, or custom headers), it automatically issues a preflight OPTIONS request to verify that the server accepts the upcoming request. The server must respond to this OPTIONS request with the appropriate CORS headers to permit the actual request.

  • Express (Node.js): The cors middleware handles preflight requests automatically. When the client sends an OPTIONS request, cors inspects the route and sends back the required headers (Access-Control-Allow-Origin, Access-Control-Allow-Methods, Access-Control-Allow-Headers, etc.) if the origin is allowed.
  • Flask (Python): Flask-CORS intercepts OPTIONS requests for routes its configuration covers. It responds with the defined CORS headers so that the browser can proceed. If you manually handle OPTIONS, ensure you add the same CORS headers before returning a response.
  • Spring Boot (Java): When you define CORS mappings via CorsRegistry or @CrossOrigin, Spring Boot will automatically generate the OPTIONS responses with the correct Access-Control-* headers, provided the requested origin and method match the allowed configuration. No additional code is needed unless you want a custom OPTIONS handler.

Failing to reply correctly to preflight requests results in the browser blocking the subsequent actual request. Ensure any custom middleware or filters do not inadvertently strip out CORS headers in OPTIONS responses.

Common CORS Configuration Pitfalls

1. Using * with Credentials

Setting Access-Control-Allow-Origin: * while also sending Access-Control-Allow-Credentials: true is not allowed by the CORS specification. Browsers will reject such configurations and block the request. Instead, you must specify explicit origins to enable credentials like cookies or HTTP authentication.

2. Neglecting to Handle Preflight Requests

Any cross-origin request that uses non-simple methods (e.g., PUT, DELETE) or custom headers triggers a preflight. The browser won't proceed with the actual request if your server does not respond with the necessary CORS headers for OPTIONS. Confirm that your CORS middleware or framework configuration includes support for handling OPTIONS requests.

3. Overly Permissive Settings

Allowing all origins (*) or methods without restriction can open security vulnerabilities. Attackers could exploit such leniency to interact with your API from malicious sites. Always follow the principle of least privilege, only permit known, trusted origins and only the HTTP methods and headers strictly needed by your application.

4. Mismatched Headers

If the client sends a custom header (e.g., X-Custom-Auth) but your server’s Access-Control-Allow-Headers does not include that header, the browser will block the request. Double-check that your allowed headers list matches all client-side headers.

5. Misplacing CORS Middleware Order

In frameworks like Express, the order in which middleware is applied matters. If app.use(cors()) is placed after route handlers or error middleware, some routes may never send the proper CORS headers. Always register CORS middleware early in the middleware chain, before defining routes.

Best Practices for CORS Configuration

Properly configuring Cross-Origin Resource Sharing (CORS) is essential for securing REST APIs and ensuring seamless cross-origin communications. Misconfigurations can lead to security vulnerabilities, unauthorized data access, and integration issues. Below are best practices to guide developers in setting up CORS effectively.

1. Restrict Allowed Origins

Avoid using wildcards (*) in the Access-Control-Allow-Origin header, especially for APIs that handle sensitive data or require authentication. Instead, specify exact origins that are permitted to access your resources.

Example:

const allowedOrigins = ['https://example.com', 'https://app.example.com'];
app.use(cors({
  origin: function (origin, callback) {
    if (allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  }
}));

This approach ensures that only trusted domains can interact with your API, reducing the risk of unauthorized access.

2. Limit HTTP Methods and Headers

Specify only the HTTP methods and headers necessary for your API operations. Overly permissive settings can expose your API to potential misuse.

Example:

app.use(cors({
  methods: ['GET', 'POST'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

By restricting methods and headers, you minimize the attack surface of your API.

3. Handle Preflight Requests Properly

Browsers send preflight OPTIONS requests to determine if the actual request is safe to send. Ensure your server correctly handles these requests by responding with appropriate CORS headers.

Example:

app.options('*', cors());

Proper handling of preflight requests prevents unexpected errors and ensures smooth cross-origin interactions.

4. Avoid Using null or Wildcard Origins

Setting Access-Control-Allow-Origin to null or using wildcards can inadvertently grant access to untrusted sources. Always specify exact origins to maintain control over who can access your API.

Example:

app.use(cors({
  origin: 'https://trusted.example.com'
}));

This practice helps prevent unauthorized access and potential security breaches.

5. Implement Environment-Based Configurations

Different environments (development, staging, production) may require distinct CORS settings. Configure your application to apply appropriate CORS policies based on the environment.

Example:

const corsOptions = process.env.NODE_ENV === 'production' ? {
  origin: 'https://prod.example.com'
} : {
  origin: 'http://localhost:3000'
};
app.use(cors(corsOptions));

This approach ensures that your API is accessible during development while maintaining strict access controls in production.

6. Regularly Audit and Monitor CORS Policies

Periodically review your CORS configurations to ensure they align with your security requirements. Use logging and monitoring tools to detect and respond to unauthorized access attempts.

Example:

app.use((req, res, next) => {
  console.log(`CORS request from origin: ${req.headers.origin}`);
  next();
});

Monitoring helps identify potential misconfigurations and unauthorized access patterns.

7. Combine CORS with Other Security Measures

CORS should be part of a comprehensive security strategy. Implement additional measures such as authentication, authorization, rate limiting, and input validation to protect your API. Integrating multiple security layers enhances the overall protection of your API.

Example:

app.use(cors(corsOptions));
app.use(authMiddleware);
app.use(rateLimiter);

Testing Your CORS Configuration

Testing your CORS (Cross-Origin Resource Sharing) setup is essential to confirm that browsers will allow legitimate cross-origin requests and block unauthorized ones. Below are several methods, using browser developer tools, cURL, Postman, and online utilities, accompanied by tips for troubleshooting common errors. 

Each approach verifies different aspects of your CORS policy to ensure it operates as intended.

Using Browser Developer Tools

Browsers enforce CORS at the client side, so the most direct way to validate your configuration is through built-in developer tools. In Chrome or Firefox, open the Network tab and observe requests that would trigger CORS checks, in particular, requests from a different origin than your page.

  • Inspecting Headers
    Navigate to the Network tab in Chrome DevTools (or Firefox DevTools). Reload the page or perform the action that sends a cross-origin request. Look for the request’s Initiator column to confirm it originates from a different domain, then click the request and inspect the Response Headers pane.
    You should see headers like Access-Control-Allow-Origin, Access-Control-Allow-Methods, and, if credentials are permitted, Access-Control-Allow-Credentials.
  • Preflight (OPTIONS) Requests
    If your request uses methods other than GET, HEAD, or POST, or sends custom headers, the browser will send an OPTIONS preflight request first. In dev tools, filter by Name: OPTIONS and examine the response headers. A correct policy will return the same CORS headers you configured on the actual endpoint, and a status code of 204 No Content or 200 OK.
  • Console Errors
    Any CORS-related blockages appear as errors in the Console tab. A typical error is:
Access to fetch at 'https://api.example.com/data' from origin 'https://frontend.example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

This message confirms that the server’s response did not include the necessary Access-Control-Allow-Origin header.

Using cURL

cURL is a command-line tool that does not enforce browser policies, but you can simulate browser-like CORS requests by manually adding the Origin header and, if needed, the Access-Control-Request-* headers for preflight testing.

  • Simple GET Request
    To check if an endpoint allows cross-origin GET requests, run:
curl -i -X GET "https://api.example.com/data" -H "Origin: https://frontend.example.com"
  • If CORS is enabled, the response headers contain Access-Control-Allow-Origin: https://frontend.example.com (or * if you configured a wildcard). If the header is missing, the browser would block the request.
  • Preflight OPTIONS Request
    For endpoints requiring a preflight check, use:
curl -i -X OPTIONS "https://api.example.com/data" \
  -H "Origin: https://frontend.example.com" \
  -H "Access-Control-Request-Method: POST" \
  -H "Access-Control-Request-Headers: Content-Type, Authorization"
  • A properly configured server returns CORS headers like Access-Control-Allow-Methods: GET, POST, PUT, DELETE and Access-Control-Allow-Headers: Content-Type, Authorizationwith a 204 or 200 status code. If any required header is missing or methods aren’t allowed, the browser would block the subsequent request.
  • Advantages of cURL
    Because cURL ignores browser-enforced CORS policies, it’s useful for isolating server-side configuration. You can directly inspect response headers without the “noise” of client-side enforcement, pinpointing misconfigurations quickly.

Using Aspen

Aspen is a free, macOS-native API testing app built by Treblle specifically for REST APIs. Unlike browsers, Aspen does not enforce CORS restrictions, allowing you to inspect response headers directly without the browser blocking the call. 

Additionally, Aspen offers a simple interface for configuring request parameters, HTTP method, URL, headers, query parameters, and body, so you can emulate cross-origin requests by manually setting the Origin header and any preflight headers.

1. Checking Response Headers

  1. Open Aspen and Create a New Request
    Launch Aspen and click “New Request.” Enter your API endpoint (e.g., https://api.example.com/data) and select the desired HTTP method (e.g., GET).
  2. Add the Origin Header
    In Aspen’s request configuration pane, scroll to the Headers section. Add a header named Origin with the value of your front-end domain (e.g., https://frontend.example.com). This is important because browsers automatically add Origin for cross-origin fetches. By explicitly setting Origin, you simulate what the browser would send, allowing you to confirm whether your API’s response includes the correct CORS headers.
  3. Send the Request and Inspect Response Headers
    Click Send. Once Aspen returns the response, switch to the Response tab and inspect the Response Headers. You should see Access-Control-Allow-Origin: https://frontend.example.com (or * if you configured a wildcard, though wildcards won’t work with credentials). If Access-Control-Allow-Origin is missing or incorrect, the browser would block the request even though Aspen shows a valid response. This indicates the server’s CORS configuration needs adjustment.

2. Simulating Preflight (OPTIONS) Requests

  1. Switch Method to OPTIONS
    In Aspen, change the method dropdown from GET (or whichever method you’re testing) to OPTIONS, which mimics the browser’s preflight request.
  2. Set Required Preflight Headers
    Add the following headers under Headers:
    • Origin: https://frontend.example.com
    • Access-Control-Request-Method: POST (replace POST with the actual method you intend to use)
    • Access-Control-Request-Headers: Content-Type, Authorization (list any custom headers your client will send).
  1. Send the Preflight Request
    Click Send. Inspect the Response Headers to confirm that:
    • Access-Control-Allow-Origin: https://frontend.example.com (or the exact origin you’ve allowed).
    • Access-Control-Allow-Methods includes the method from Access-Control-Request-Method (e.g., POST, PUT, etc.).
    • Access-Control-Allow-Headers lists any requested headers (e.g., Content-Type, Authorization).
    • If credentials (cookies or HTTP authentication) are required, check that Access-Control-Allow-Credentials: true is present (and ensure Access-Control-Allow-Origin is not *).
  1. Interpret the Results
    • Success: If all required CORS headers appear in the OPTIONS response, the actual request (e.g., POST /data) will succeed in browsers.
    • Failure: If any header is missing or the status code is not 200 OK or 204 No Content, the browser will block the real request. Use this feedback to update your server’s CORS configuration (e.g., adjust middleware settings or response header logic).

Advantages of Using Aspen

  • Zero-Trust Policy: Aspen performs all operations locally and does not require login, ensuring your API credentials and test data remain private.
  • AI-Assisted: With the integrated Alfred AI assistant, you can automatically generate data models, OpenAPI specs, and integration code based on your test request, speeding up development and debugging.
  • Lightweight and Fast: Aspen is optimized for Apple Silicon and consumes minimal memory, making it noticeably faster than many alternative tools.
  • Custom Header Configuration: Aspen’s UI allows you to quickly add or modify headers, crucial for testing CORS scenarios where you need to replicate browser behavior.

Online CORS Testing Tools

Several web-based tools simplify CORS testing by issuing requests and displaying header information, eliminating the need for manual command-line steps or browser setup.

  • CORS Test by CodeHappy:  The CORS Tester tool allows you to input your API URL, specify an origin, and select an HTTP method (e.g., GET, POST, OPTIONS). It then displays all request and response headers, highlighting missing or incorrect CORS headers.

These tool is quick for ad-hoc checks, especially when you don’t have local access to a terminal or prefer a graphical interface. 

Conclusion

Configuring Cross-Origin Resource Sharing (CORS) correctly is crucial for securing your REST API and ensuring seamless integration with front-end applications. By understanding the principles of CORS, implementing best practices, and thoroughly testing your configuration, you can prevent common pitfalls and enhance the security of your API.

For developers seeking an efficient and privacy-focused tool to test and debug CORS settings, Aspen by Treblle offers a compelling solution. Aspen is a free, macOS-native API testing application that operates entirely offline, ensuring your data remains private. 

Its intuitive interface allows you to simulate cross-origin requests, inspect response headers, and validate your CORS configuration without the need for browser-based tools. Additionally, Aspen's integrated AI assistant can generate data models and integration code, streamlining your development workflow.

💡
Debug CORS errors faster with Aspen by Treblle. Aspen lets you test cross-origin requests locally without browser restrictions, so you can fix issues before they reach production.

Spread the word

Keep reading