StackHawk
๏ƒ‰

What is CORS? A Complete Guide to Cross-Origin Resource Sharing

Scott Gerlach   |   Jul 21, 2025

LinkedIn
X (Twitter)
Facebook
Reddit
Subscribe To StackHawk Posts

Developers are primarily familiar with CORS due to the errors they frequently encounter as a result of it. Chances are, you landed here because you, too, are experiencing a CORS issue that you need to understand and resolve. CORS, or Cross-Origin Resource Sharing, is a browser security feature that controls how web pages can request resources from domains other than their own. This mechanism protects users and servers from potentially malicious cross-site access, but it can also trip up developers when building modern applications.

In this article, we’ll explain CORS, show how it works under the hood, and offer best practices for configuring it securely, whether you’re building a front-end app or securing APIs.

What Is CORS and Why Does It Exist?

Cross-Origin Resource Sharing (CORS) is a mechanism that allows web applications to make requests to domains different from the one serving the original web page. CORS policies are defined on the server, but they are enforced by the browser. CORS hasnโ€™t always existed, and to understand why CORS exists, we need to first understand what came before it.

The Same-Origin Policy Foundation

Before CORS, browsers enforced a strict “Same-Origin Policy” that dictated:

  • Scripts loaded from a particular domain could only make requests to that same domain
  • This prevented www.example.com from making requests to api.differentsite.com

While secure, this limitation made it difficult to build modern web applications, which typically require multiple servers and domains to operate. Therefore, this approach quickly became obsolete.

Why CORS Became Necessary

As web applications evolved, developers needed legitimate ways to:

  • Host static content at www.example.com and backend APIs at api.example.com
  • Integrate with third-party services and APIs
  • Build microservices architectures with multiple domains

Unlike the days of serving applications on a single server and a single origin (or domain), these applications required the ability to handle a multi-origin approach. This is where CORS came in to offer a controlled way to relax the Same-Origin Policy, enabling cross-origin requests while maintaining security through explicit permission mechanisms.

An important note is that since CORS is enforced at the browser level, using tools like Postman to test an API request or resources on the server doesnโ€™t get caught up in CORS. This explains why many developers can test their API endpoint without issue in these types of tools and then encounter CORS issues when using the endpoint in their browser-based application.

How Does CORS Work? A Step-by-Step Breakdown

Understanding how CORS works is crucial for both developers and security professionals. The CORS workflow involves a “preflight” process that validates cross-origin requests before they’re executed.

The CORS Preflight Process

Here’s exactly what happens when a cross-origin request is made:

  1. JavaScript attempts a cross-origin request from frontend.example.com to api.example.com
  2. The browser automatically sends an OPTIONS preflight request to the target server
  3. The server responds with CORS headers indicating what’s allowed
  4. The browser decides whether to allow the actual request based on the server’s response
A diagram of the CORS Preflight Process

Real-World CORS Example

Building on the principles from above, letโ€™s look at a real-world example where a front-end application, running in a browser, makes a request to a /users API that sits on another domain, like so:

Origin: https://frontend.example.com
Request: POST https://api.example.com/users

The browser first sends a pre-flight check, letting the server know the origin of the request and the endpoint it is trying to perform the POST request on:

OPTIONS /users HTTP/1.1
Host: api.example.com
Origin: https://frontend.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type

The server then responds with a pre-flight response, which lets the browser know which origins, via the Access-Control-Allow-Origin header, are allowed (and the methods as well, in this case):

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://frontend.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type

Once the browser receives this response and verifies that the origin, method, and headers are permitted, it will proceed with the actual POST request. If the origin isnโ€™t permitted, the browser will then block it and show an error message in the browser’s console. 

It is essential to note, however, that not all cross-origin requests require a preflight. Simple requests, such as GET or POST with standard headers and content types like text/plain or application/x-www-form-urlencoded, are sent directly without a preflight check.

CORS Request Headers Explained

Before an actual request can be sent, the preflight request must be sent from the browser to the target server. When browsers make preflight requests, they include specific headers that servers must validate:

Origin Header

  • Purpose: Identifies the domain making the request
  • Example: Origin: https://frontend.example.com
  • Security Note: Should always be validated against an allowlist

Access-Control-Request-Method

  • Purpose: Specifies the HTTP method for the actual request
  • Example: Access-Control-Request-Method: POST
  • Best Practice: Validate against supported API methods

Access-Control-Request-Headers

  • Purpose: Lists custom headers the request wants to send
  • Example: Access-Control-Request-Headers: Authorization, Content-Type
  • Reliability: Not always reliable, as actual requests may vary

CORS Response Headers Explained

Once the server processes the request, it then sends a response back to the browser that made the request. Server responses include headers that define what cross-origin requests are permitted, such as:

Access-Control-Allow-Origin

  • Purpose: Specifies which origins can access the resource
  • Valid Values: Specific origin or * (wildcard)
  • Security Critical: Must match request origin exactly, wildcard not recommended in production environments

Access-Control-Allow-Methods

  • Purpose: Lists permitted HTTP methods
  • Example: Access-Control-Allow-Methods: GET, POST, PUT, DELETE
  • Best Practice: Only include methods your API actually supports

Access-Control-Allow-Headers

  • Purpose: Specifies allowed request headers
  • Example: Access-Control-Allow-Headers: Authorization, Content-Type
  • Usage: Should reflect preflight request headers or use a wildcard

Access-Control-Allow-Credentials

  • Purpose: Boolean indicating if credentials (cookies, auth headers) are allowed
  • Values: true or omitted (defaults to false)
  • Security Note: Cannot be true when Access-Control-Allow-Origin is *

Access-Control-Max-Age

  • Purpose: Specifies how long browsers should cache preflight responses
  • Example: Access-Control-Max-Age: 3600 (1 hour)
  • Benefit: Reduces preflight request overhead

Access-Control-Expose-Headers

  • Purpose: Specifies which response headers client scripts can access
  • Example: Access-Control-Expose-Headers: X-Custom-Header
  • Default: Only basic headers are exposed to scripts

The browser then processes the values within these headers and enforces the CORS policy based on them. If the request from the browser is not permitted, as indicated by these headers, it will not be sent, and an error will be thrown within the browser.

CORS Best Practices for Secure Implementation

As mentioned, configuring CORS properly, without using insecure or development-only workarounds, means adding or changing code on the server itself where the request originates. At the server level, implementing CORS securely requires careful attention to configuration details. Letโ€™s look at a few ways that this could be done on a NodeJS server that can be easily rolled out to other languages and frameworks in a similar fashion, and a few things you should not do.

Validate Origins Against a Whitelist

One of the most common ways to create a CORS configuration that will work well and support multiple domains is to use an origin whitelist. In the code below, you will see that weโ€™ve created an array of allowed origins and then compare the request origin (the origin in the browser) to those in the whitelist. If there is a match, the Access-Control-Allow-Origin header will be returned with that value, and the browser will then send the actual request since the preflight checks have passed.

const allowedOrigins = [
  'https://frontend.example.com',
  'https://app.example.com',
  'https://admin.example.com'
];

if (allowedOrigins.includes(requestOrigin)) {
  response.setHeader('Access-Control-Allow-Origin', requestOrigin);
}

Never Use Wildcards with Credentials

Often, when working on local development, developers set the Access-Control-Allow-Origin header to the wildcard (*) value. While this can be fine for local development, this will allow every origin to make requests to the server, and in doing so, will completely eliminate any security that CORS would offer. So, in short:

  • Wrong: Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true
  • Right: Validate origin and reflect specific domain when credentials are needed

Use Secure String Comparison

When creating your CORS logic and using string comparisons within the logic, youโ€™ll want to do the following:

  • Avoid regex patterns that can be bypassed
  • Instead, use exact string matching for origin validation
  • Consider subdomain policies carefully

Implement Logging and Alerting

Logging of CORS events and subsequent alerting is also a smart idea to add to your server-side logic. Although some blocked requests may not be malicious, others could be. When a CORS request is blocked, it should be logged on the server. You can then use your preferred log parsing tool to alert your development or security team to a potential security risk that is unfolding. Hereโ€™s an example of what that may look like in NodeJS:

if (!allowedOrigins.includes(requestOrigin)) {
  console.log(`Blocked CORS request from: ${requestOrigin}`);
  // Alert security team for potential attack
}

Common CORS Misconfigurations and Security Risks

Although CORS may cause headaches for developers at certain points in the development lifecycle, it does serve an important purpose. Understanding how CORS security vulnerabilities work helps prevent dangerous misconfigurations. Letโ€™s take a deeper look at some insecure misconfigurations to be aware of:

The Wildcard Danger

Setting Access-Control-Allow-Origin: * effectively disables the Same-Origin Policy:

  • Risk: Any malicious site can make requests to your API
  • Impact: Potential data theft and unauthorized actions
  • Solution: Always validate and reflect specific origins

Blind Origin Reflection

Reflecting the Origin header without validation is worse than wildcards:

// DANGEROUS - Don't do this!
response.setHeader('Access-Control-Allow-Origin', request.headers.origin);

Why it’s dangerous: It bypasses browser protections and allows credentials to be used with any origin.

Real-World Attack Scenario

CORS attacks do occur, and they can be challenging to detect, especially for users who unknowingly use the compromised application in their browser. Here is how such an attack may unfold:

Diagram of a real-world CORS attack scenario
  1. The victim visits a malicious site (through phishing or a malicious ad)
  2. Evil JavaScript runs on the malicious page
  3. The script makes requests to a vulnerable API with the victim’s cookies
  4. API responds due to misconfigured CORS
  5. Attacker steals data or performs unauthorized actions

Of course, CORS is one piece of the security puzzle, and the above scenario likely isnโ€™t quite as simple to execute as weโ€™ve made it seem. But that said, if the application has multiple security issues, having CORS as a frontline defense could help stop this attack in its tracks.

How to Securely Enable CORS

Almost every framework and language that supports server-side development has a way to easily implement and handle CORS. Letโ€™s take a look at a code example, some popular frameworks that make handling CORS easy, and some testing and validation tips.

Validated Origin Reflection Example

Looking further at an example of how to implement CORS policies in JavaScript. Again, using your language or framework of choice, you can transcribe this same pattern to work effectively. Here is an example of a setCORSHeaders function that takes the request’s origin, compares it to a whitelist, and if it exists, then reflects the validated origin back to the browser in the response.

function setCORSHeaders(request, response) {
  const origin = request.headers.origin;
  const allowedOrigins = [
    'https://frontend.example.com',
    'https://app.example.com'
  ];
  
  if (allowedOrigins.includes(origin)) {
    response.setHeader('Access-Control-Allow-Origin', origin);
    response.setHeader('Access-Control-Allow-Credentials', 'true');
  } else {
    response.status(403).send('Forbidden');
  }
}

Framework Libraries and Reverse Proxies

When configuring CORS, many libraries exist to simplify these configurations (and also help with secure configurations), as well as the ability to configure it on an API gateway or secure proxy, such as NGINX.

Across different frameworks, some of the most popular CORS libraries include:

  • Express.js: cors middleware
  • Django: django-cors-headers
  • Spring Boot: @CrossOrigin annotation
  • Laravel: Built-in CORS middleware

When configuring this at the gateway or reverse proxy level, this can be done in the proxy’s configuration or, in some cases, via UI. In the case of NGINX, here is how simple this type of configuration is to implement:

location /api {
  if ($http_origin = "https://frontend.example.com") {
    add_header Access-Control-Allow-Origin $http_origin;
    add_header Access-Control-Allow-Credentials true;
  }
}

Testing and Validation Tips

Regardless of how you decide to configure your CORS policies, here are a couple of ways you can test and validate it to make sure it matches the expected behaviour and is secure:

  • Use browser dev tools to inspect CORS headers
  • Test with different origins to ensure validation works
  • Verify preflight responses match your expectations
  • Check credential handling with authentication

How StackHawk Can Help with CORS and API Security

While proper CORS configuration is essential, it’s just one piece of your application security puzzle. Even with CORS properly configured, your APIs may still be vulnerable to other security issues.

StackHawk’s dynamic application security testing (DAST) helps identify security vulnerabilities, such as CORS Misconfiguration, that can affect your APIs. 

On top of this, it can also help identify other critical vulnerabilities such as:

  • Cross-Site Request Forgery (CSRF): Protects against malicious sites forcing authenticated actions
  • Cross-Site Scripting (XSS): Detects code injection vulnerabilities that could bypass CORS protections
  • Authentication flaws: Identifies weak authentication mechanisms that CORS relies on
  • API-specific vulnerabilities: Finds issues unique to REST and GraphQL APIs

Key Benefits:

  • Automated scanning in CI/CD pipelines
  • Developer-friendly remediation guidance
  • Comprehensive vulnerability detection beyond CORS
  • Integration with existing security tools

Getting Started with StackHawk

To start testing applications for CORS vulnerabilities with StackHawk, youโ€™ll need an account. You canย sign up for a trial account. If youโ€™re using an AI-coding assistant like Cursor or Claude Code, sign up for our $5/month single-user plan,ย Vibe, to find and fix vulnerabilities 100% in natural language.

Configuring CORS in Your Stack: Framework-Specific Guides

Whether you’re encountering CORS errors in development or need to configure production-ready CORS policies, we’ve created detailed guides for the most popular tools and frameworks:

Front-End CORS Configuration:

  • Angular CORS – Handle cross-origin requests in Angular applications
  • TypeScript CORS – Type-safe CORS handling patterns
  • React CORS – Resolve common CORS issues in React apps
  • Vue.js CORS – Configure Vue applications for cross-origin requests

Server-Side CORS Configuration:

FEATURED POSTS

Security Testing for the Modern Dev Team

See how StackHawk makes web application and API security part of software delivery.

Watch a Demo

Subscribe to Our Newsletter

Keep up with all of the hottest news from the Hawkโ€™s nest.

"*" indicates required fields

More Hawksome Posts