What is CORS (Cross-Origin Resource Sharing) and how does it work?

What is CORS (Cross-Origin Resource Sharing) and how does it work?

Write to us

In the architecture of the modern Internet, where web applications dynamically retrieve data from a wide variety of sources and application programming interfaces (APIs) are the backbone of communication, secure management of access to resources is absolutely crucial. One of the fundamental yet often misconfigured mechanisms that guards this security is CORS (Cross-Origin Resource Sharing). For many developers, CORS-related errors are a source of frustration, but understanding how it works is essential to building secure and functional applications.

CORS is a mechanism that relaxes, in a controlled manner, the restrictive security policy built into every web browser, known as the Same-Origin Policy. It is this rule that protects users from malicious sites that might try to steal their data from other sites open in the background. In this article, we will explore what CORS is, what problem it solves, how its mechanisms work at the HTTP header level and why its improper configuration can open the door to serious attacks. We’ll explain how to avoid the most common mistakes and how a security audit can become a key part of verifying your digital fortress.

What is CORS and what web browser security problem does it solve?

CORS, or Cross-Origin Resource Sharing, is a mechanism built into web browsers that allows a server to controllably allow a website from one domain (origin) to access resources located in another domain. It is a technical standard that adds new HTTP headers, allowing servers to describe which “origins” (domains, schemas, ports) can request access to resources from a browser.

The problem that CORS solves is directly related to a fundamental security principle of web browsers known as Same-Origin Policy (SOP), or ” same-origin policy.” SOP is a built-in defense mechanism that, by default, blocks scripts running on one website (e.g., https://moja-aplikacja.com) from reading data from another website (e.g., https://api.partnera.com). If this rule were not in place, a malicious script on the site you opened could, without your knowledge, send a request to your online bank (open in another tab) and read sensitive data.

With the growth of the Internet and the advent of Single-Page Applications (SPAs) and microservices, the strict limitations of SOPs became an issue. Developers needed a way to allow legitimate communication between different domains in a secure and controlled manner – for example, so that a front-end application hosted in one domain could retrieve data from an API hosted in another. CORS is precisely the answer to this problem. It acts as an intermediary mechanism: the browser, seeing that a script is trying to make a request to another origin, first “asks” the destination server via special HTTP headers whether it agrees to such an operation. It is the server, not the browser, that has the final word, which allows secure sharing of resources.

What is the Same-Origin Policy and why is it so important?

Same-Origin Policy (SOP) is one of the most important pillars of web security. It is a mechanism built into every modern web browser that restrictively defines how a document or script loaded from one “origin” can interact with a resource from another “origin.” “Origin” (origin) here is defined by a combination of three elements: schema (protocol), host (domain) name and port. If any of these three parts differ, the browser treats the resource as coming from different origins.

In practice, SOP means that a JavaScript running on https://www.nfIo.pl can freely read data from https://www.nfIo.pl/baza-wiedzy, since the origin is the same. However, the same script, trying to send a request (e.g. of the fetch or XMLHttpRequest type) to read content from https://api.nfIo.pl (a different subdomain) or http://www.nfIo.pl (a different scheme), will be blocked by the browser by default. The browser will send the request, but will block the script from accessing the response, thus protecting the data.

The importance of this principle is fundamental to protecting user privacy and security. Imagine a scenario without SOP: You log in to your online bank account. In another browser tab, you open a page that contains a malicious script. This script could send a request to the bank’s server, and since you are logged in, the request would be authenticated with your cookies. The script could then read a response containing your transaction history or account balance and send that data to the attacker’s server. Same-Origin Policy prevents just such attacks by acting as a built-in shield to protect your online sessions.

How does the CORS mechanism allow secure sharing of resources between different domains?

The CORS (Cross-Origin Resource Sharing) mechanism acts as a superstructure over the restrictive Same-Origin Policy (SOP), allowing controlled and safe exceptions to its rules. Instead of completely blocking requests between different origins, CORS introduces an HTTP header-based communication protocol that allows the destination server to declare that it trusts requests from specific other origins. The key point is that the decision to share a resource is made on the server side, which owns the resource.

When a script on site A (https://strona-a.com) tries to make a request to a resource on site B (https://api-b.com), the browser automatically adds a special Origin header to that request that tells server B the origin of the request. In this case, the header would look like this: Origin: https://strona-a.com. This action is initiated by the browser and the developer of site A has no influence on it.

Server B, upon receiving the request, analyzes the Origin header. If the server is configured to trust requests from the https://strona-a.com domain, it appends the Access-Control-Allow-Origin header to its response. It can contain a specific domain (Access-Control-Allow-Origin: https://strona-a.com) or a wildcard character (*), indicating that it agrees to requests from any domain. The browser, upon receiving the response from the B server, checks this header. If the Access-Control-Allow-Origin value matches the origin of site A (or is *), the browser passes the response to the script. Otherwise, although the response from the server has arrived, the browser blocks access to it and throws a CORS-related error in the console, effectively protecting the data.

What are the most common CORS configuration errors that can lead to security vulnerabilities?

Mistakes in CORS configuration are extremely common and can lead to serious security vulnerabilities, completely nullifying the protection the mechanism is supposed to provide. The most common and also the most dangerous mistake is the use of overly permissive settings, often resulting from a desire to quickly solve developer problems without fully understanding the consequences.

The most critical error is setting the header Access-Control-Allow-Origin: * in combination with allowing credentials (Access-Control-Allow-Credentials: true). This combination is fortunately blocked by most browsers, but developers often try to get around it by dynamically mirroring the Origin header value from the request in the Access-Control-Allow-Origin response. Such a setup, seemingly working, in practice means that any malicious website can send authenticated requests to our API and read sensitive data belonging to the logged-in user. This is a straightforward path to Cross-Site Request Forgery (CSRF) attacks on steroids.

Other common errors include failing to validate the value of the Origin header when dynamically mirroring it. An attacker can craft the Origin header in a request to fool the server if the server doesn’t check it against a predefined, secure “whitelist” of trusted domains. Another problem is overly broad permissions on HTTP methods (Access-Control-Allow-Methods) or headers (Access-Control-Allow-Headers). Allowing potentially dangerous methods (e.g., DELETE) from any origin can lead to unauthorized data modifications. It is also a mistake to rely on CORS as the only authorization mechanism – CORS controls who (what domain) can send a request from a browser, but it is not a substitute for mechanisms that check whether a user has permission to perform a particular action.

How do the HTTP headers used in the CORS mechanism work in practice?

The CORS mechanism is entirely implemented using standard HTTP headers. Understanding the role of each of them is crucial for proper configuration and diagnosing problems. Headers can be divided into those sent by the browser (request headers) and those sent by the server (response headers).

Request headers (sent by the browser to the server):

  • Origin: This is the most important header sent with every inter-domain request. It contains the origin (scheme, host, port) of the party that initiates the request. Example: Origin: https://app.nfIo.pl.
  • Access-Control-Request-Method: Used in “preflight” requests (see next section). Tells the server which HTTP method (e.g. PUT, DELETE) will be used in the actual request.
  • Access-Control-Request-Headers: Also used in “preflight” requests. Tells the server what custom HTTP headers will be included in the actual request (e.g. Authorization, X-Custom-Header).

Response headers (sent by the server to the browser):

  • Access-Control-Allow-Origin: This is a fundamental response header. The server uses it to tell the browser which origins have permission to access the resource. It must contain either a specific origin (e.g., https://app.nfIo.pl) or the * character.
  • Access-Control-Allow-Credentials: Setting this header to true tells the browser that the server allows credentials (such as cookies or authorization headers) to be sent in inter-domain requests. For security reasons, if this header is set to true, Access-Control-Allow-Origin must specify a specific domain, not *.
  • Access-Control-Allow-Methods: Sent in response to a “preflight” request. Specifies which HTTP methods (e.g., GET, POST, PUT) are allowed in requests from a given origin.
  • Access-Control-Allow-Headers: Also part of the “preflight” response. Indicates which HTTP headers can be used in the actual request.
  • Access-Control-Max-Age: Allows the browser to cache (remember) the result of a “preflight” request for a specified period of time (in seconds), thus avoiding sending it before each subsequent request and improving performance.

What is a “preflight” (OPTIONS) request and when is it sent?

A “preflight” (probing flight) request is a key security mechanism within the CORS protocol. It is a special probing request automatically sent by the browser that precedes the actual inter-domain request. Its purpose is to verify that the target server is aware of the CORS mechanism and agrees to send the right request with certain parameters. It acts as a permission request before any potentially dangerous operation takes place.

The browser does not send a “preflight” request always. It does so only when the actual request is considered “not simple” (non-simple request). A request is “simple” if it meets all of the following conditions: it uses the GET, HEAD or POST method, it contains no custom headers other than a few basic ones, and in the case of the POST method, its Content-Type is application/x-www-form-urlencoded, multipart/form-data or text/plain. Any other request – for example, using PUT, DELETE, PATCH methods or containing non-standard headers such as Authorization (which is typical for authenticated APIs) – is “not simple” and requires a “preflight” send.

In practice, a “preflight” request is an HTTP request sent via the OPTIONS method to the same URL as the intended request proper. It contains Origin, Access-Control-Request-Method and Access-Control-Request-Headers headers. The server, upon receiving such a request, should respond using the Access-Control-Allow-Origin, Access-Control-Allow-Methods and Access-Control-Allow-Headers headers, informing the browser of its policies. If the server’s response is positive (i.e., it allows the method and headers from the given origin), the browser sends the correct request. Otherwise, it blocks the operation, protecting the server from unauthorized actions.


“Preflight” request flow

  1. Initiation (Browser): The script attempts to send a “non-straight” request (e.g., a PUT with an Authorization header) to another domain.
  2. Preflight request (Browser → Server): The browser pauses the PUT request and sends an OPTIONS request to the same URL, asking: “Can I send a PUT request with Authorization from domain X?”.
  3. Verification (Server): The server checks its CORS configuration and decides whether it trusts domain X and accepts these parameters.
  4. Preflight response (Server → Browser): The server responds with headers, such as Access-Control-Allow-Origin: domain X, Access-Control-Allow-Methods: PUT, GET, Access-Control-Allow-Headers: Authorization.
  5. Decision (Viewer): The browser compares the received permissions with the planned request.
    • If OK: Sends the correct original PUT request.
    • If Error: Blocks the PUT request and reports a CORS error in the console.

What are the risks to the company of incorrect CORS configuration, such as “Access-Control-Allow-Origin: *”?

Improper CORS configuration, and in particular the careless use of the universal permission Access-Control-Allow-Origin: *, poses a serious and real risk to the security of the company, its data and its users. Such a configuration is tantamount to symbolically taking the door off its hinges and inviting potential attackers to freely use internal application resources.

The main risk is to enable advanced Cross-Site Request Forgery (CSRF) attacks and data theft. Imagine a scenario where a company’s API, returning sensitive customer data, is secured with cookie-based authentication. If this API is misconfigured with Access-Control-Allow-Origin: *, an attacker can create a malicious website. When a victim, logged into a legitimate application, visits this website, a malicious script in the background will send a request to the company’s API. Since the browser will automatically attach cookies, the request will be authenticated and the API will return sensitive data. With a liberal CORS policy, the browser will allow the malicious script to read this data and send it to the attacker’s server.

The risk applies not only to reading data. If the API allows modification (e.g., with POST, PUT, DELETE methods), a malicious party could change a user’s password, email address, make a financial transaction or delete an account on behalf of the user. In the context of internal systems, a misconfiguration of CORS can allow attacks on the infrastructure behind the firewall. An attacker could use an employee’s browser as a “gateway” to send requests to unsecured systems on the local network that are not normally accessible from the Internet. Such a setup completely undermines fundamental security principles and can lead to catastrophic breaches.

How to securely configure CORS for web applications and APIs?

A secure CORS configuration is based on the principle of least privilege – allow access only to those origins, methods and headers that are absolutely necessary for the proper functioning of the application. Using universal permissions is a shortcut that leads directly to security vulnerabilities.

The most important rule is to use a precise, dynamic whitelist of trusted origins. Instead of using Access-Control-Allow-Origin: *, the server should verify the Origin header value from the request and compare it with a predefined list of domains that have access rights. If Origin is on the list, the server should return it in the Access-Control-Allow-Origin header. If not, the request should be rejected. This approach ensures that only trusted front-end applications can communicate with the API.

The allowed methods and headers should also be precisely defined. The Access-Control-Allow-Methods header should list only those HTTP methods that are actually used by the API (e.g., GET, POST, PUT), not a whole list of them. Similarly, the Access-Control-Allow-Headers header should list only those custom headers that are necessary (e.g. Content-Type, Authorization), and not allow all of them. If the application requires the transmission of credentials (cookies, tokens), set Access-Control-Allow-Credentials: true, keeping in mind that in this case Access-Control-Allow-Origin absolutely must include a specific trusted domain. Regular review and auditing of the CORS configuration is key to ensure that it remains relevant to the changing needs of the application.

What developer and tester tools help diagnose CORS problems?

Diagnosing CORS problems can be frustrating, but fortunately, developers and testers have a number of tools at their disposal to make the process significantly easier. The most important and commonly used tool is the developer tools built into web browsers (e.g. Chrome DevTools, Firefox Developer Tools). The “Console” (Console) tab shows detailed CORS error messages that explain exactly why the request was blocked (e.g. missing Access-Control-Allow-Origin header, forbidden method, etc.). The Network tab, on the other hand, provides a detailed analysis of each request and response, including all HTTP headers, allowing you to see what CORS headers the browser sent and what the server responded to.

For testing and simulating API requests, tools such as Postman or Insomnia are extremely useful. They allow you to manually create HTTP requests (including OPTIONS for “preflight”) and set arbitrary headers (e.g. Origin), allowing you to test CORS logic on the server side without using a browser. In this way, you can test how the API responds to requests from different, including unauthorized, origins.

The security testing process uses more advanced tools such as Burp Suite or OWASP ZAP. They act as proxies that intercept all traffic between the browser and the server, allowing it to be analyzed and modified on the fly. Testers can use them to manipulate CORS headers in an attempt to bypass security, and verify that the server is immune to misconfiguration attacks. Libraries and frameworks that allow scripts to be written to verify the correctness of CORS headers in server responses can also be used to automate testing.

Is CORS the only mechanism for controlling access to web resources?

Definitely not. CORS is very important, but only one of the many mechanisms that make up a multi-layered strategy for controlling access to web resources. It is crucial to understand its specific role: CORS is a browser security mechanism that controls whether a script from one domain can read a response from another domain. It is not a server-side authentication mechanism. Relying solely on CORS to secure an API is a serious mistake.

The basic access control mechanism is authentication and authorization. Authentication answers the question “who are you?” (e.g., through password logins, JWT tokens, API keys), and authorization answers the question “what are you allowed to do?”. Even if a CORS policy allows a request to be sent, it is the server, based on the user’s credentials, that must verify that the user has permission to read or modify the resource. These mechanisms work independently of CORS and are absolutely essential.

Another important mechanism is Content Security Policy (CSP). This is another set of HTTP headers that allows you to control from which sources a browser can load resources (scripts, styles, images) on a given page. CSP helps protect against Cross-Site Scripting (XSS) attacks by limiting the possibility of executing malicious code. In the context of modern applications, advanced API Gateways are also used, which can centrally manage access policies, limit the number of requests (rate limiting) and integrate with identity management systems, creating an additional layer of defense against the application server.

What are the best practices for securing modern web applications?

Securing modern web applications requires a comprehensive, multi-layered approach that takes into account the entire application lifecycle – from design to development to deployment and maintenance. Relying on a single technology or mechanism is insufficient. It is crucial to implement a defense-in-depth strategy, where multiple independent layers of security protect against a variety of attack vectors.

The foundation is secure coding (secure coding). Developers must be aware of the most common vulnerabilities, such as those described in the OWASP Top 10 list (e.g., Injection, Broken Authentication, Cross-Site Scripting), and employ practices that prevent them. This includes validating all user input, using parameterized database queries (prepared statements) to protect against SQL Injection, and correctly encoding output to prevent XSS. Equally important is dependency management – regularly scanning and updating third-party libraries used by the application to eliminate known vulnerabilities.

Another pillar is strong authentication and session management. Policies for complex passwords should be enforced, multi-factor authentication (MFA) should be offered, and secure, stateless session mechanisms such as JWT tokens should be used. Proper configuration of the server and HTTP security headers, such as Content Security Policy (CSP), HTTP Strict Transport Security (HSTS) or just CORS, is also essential. The whole thing should be complemented by regular automated security scans (SAST/DAST) on a CI/CD cycle and periodic manual penetration tests by external experts.

How can web application security audits from nFlo help your company properly and securely configure mechanisms such as CORS?

Web application security audits performed by nFlo are a comprehensive service that allows you to objectively and thoroughly verify the security status of your application, including key mechanisms such as CORS. Our approach goes far beyond a simple automated scan. We combine advanced tools with manual analysis and years of experience from our experts to identify not only commonly known vulnerabilities, but also subtle logic and configuration errors that automated scanners may miss.

In the context of CORS, our specialists carefully analyze the server-side configuration. We verify that the Access-Control-Allow-Origin policy is based on a secure “whitelist” and not on risky universal permissions. We verify that the application is resistant to attempts to bypass security by manipulating the Origin header. We also analyze the configuration of the other CORS headers, making sure that only necessary methods and headers are allowed, which is in accordance with the principle of least privilege.

The result of our audit is a detailed report that not only lists the identified vulnerabilities, but also assesses their risk level in the context of your business and, most importantly, provides specific, technical recommendations for remediation. We provide you with practical guidance on how to securely configure CORS and other security mechanisms to protect your APIs and user data. When you invest in an nFlo audit, you not only gain confidence in the security status of your application, but also the knowledge and support of a partner who can help you build a robust, attack-resistant web architecture.

About the author:
Justyna Kalbarczyk

Justyna is a versatile specialist with extensive experience in IT, security, business development, and project management. As a key member of the nFlo team, she plays a commercial role focused on building and maintaining client relationships and analyzing their technological and business needs.

In her work, Justyna adheres to the principles of professionalism, innovation, and customer-centricity. Her unique approach combines deep technical expertise with advanced interpersonal skills, enabling her to effectively manage complex projects such as security audits, penetration tests, and strategic IT consulting.

Justyna is particularly passionate about cybersecurity and IT infrastructure. She focuses on delivering comprehensive solutions that not only address clients' current needs but also prepare them for future technological challenges. Her specialization spans both technical aspects and strategic IT security management.

She actively contributes to the development of the IT industry by sharing her knowledge through articles and participation in educational projects. Justyna believes that the key to success in the dynamic world of technology lies in continuous skill enhancement and the ability to bridge the gap between business and IT through effective communication.