API Security | Jun 15, 2025 | 18 min read
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.
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.
Need real-time insight into how your APIs are used and performing?
Treblle helps you monitor, debug, and optimize every API request.
Explore TreblleNeed real-time insight into how your APIs are used and performing?
Treblle helps you monitor, debug, and optimize every API request.
Explore TreblleCross-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.
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.
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.
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:
https://app.example.com
, making API calls to https://api.example.com
.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.
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.
In this section, we’ll walk through the steps to configure Cross-Origin Resource Sharing (CORS) for your REST API.
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.
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.
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.
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.
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.
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.
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-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.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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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 (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.
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.
Access-Control-Allow-Origin
, Access-Control-Allow-Methods
, and, if credentials are permitted, Access-Control-Allow-Credentials
.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
.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.
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.
curl -i -X GET "https://api.example.com/data" -H "Origin: https://frontend.example.com"
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.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"
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
and Access-Control-Allow-Headers: Content-Type, Authorization
with a 204
or 200
status code. If any required header is missing or methods aren’t allowed, the browser would block the subsequent request.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.
https://api.example.com/dat
a) and select the desired HTTP method (e.g., GET
).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.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.GET
(or whichever method you’re testing) to OPTIONS
, which mimics the browser’s preflight request.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).
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 *
).
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).
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.
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.
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.
Securing your first REST API doesn’t have to be complicated. In this guide, you’ll learn how to use an API key for basic authentication, and get practical tips to protect your API from misuse, even in early development.
In December 2024, Volkswagen Group faced a significant data breach caused by a misconfigured API, exposing the sensitive information of around 800,000 electric vehicle (EV) drivers. This article delves into the breach’s causes, risks, and essential lessons for API security.
APIs handle sensitive data like payments and personal info, making compliance with regulations like GDPR and CCPA essential. Treblle 3.0’s Compliance feature simplifies the process, helping you secure your API, address risks early, and build trust without adding complexity.