Data Structures and Algorithms

Cross-Origin Resource Sharing (CORS) Explained for Interviews

Cross-Origin Resource Sharing (CORS) is a key web security concept often asked about in technical interviews. At its core, CORS is an HTTP-header based mechanism that lets servers explicitly allow web browsers to load resources (like API data, fonts, images, scripts) from a different domain (origin) than the one from which the web page was served. In other words, CORS is a controlled relaxation of the browser’s Same-Origin Policy, a restrictive rule that by default prevents a site from making cross-domain requests for security reasons. Understanding CORS means knowing when browsers enforce the Same-Origin Policy, how servers can grant exceptions via headers, and what interviewers typically expect: definitions, header names, preflight behavior, and common pitfalls.

To boost your skills and get the latest course updates, sign up for our free web development courses here, and prepare thoroughly for your next interview.

Same-Origin Policy: Why CORS Exists

The Same-Origin Policy (SOP) is a browser security rule that restricts how scripts and web pages loaded from one origin (scheme + host + port) can interact with resources from another origin. Under SOP, a web app loaded from https://domain-a.com:443 can freely request other pages or APIs on domain-a.com, but cannot read responses from https://domain-b.com or even https://sub.domain-a.com (a different host) or a different port or protocol. This prevents malicious scripts on one site from accessing sensitive data on another. For example, JavaScript on https://foo.com cannot use fetch() or XMLHttpRequest to read https://bar.com/secret unless bar.com explicitly allows it.

Certain resources are exempt from SOP. For instance, you can embed images, CSS, and scripts from other origins: the browser will load them, but scripts won’t be allowed to read data from them if they come from another site. (For example, an <img> tag can display an image hosted anywhere, but drawing that image into a canvas or reading its bytes would be blocked by SOP.) Iframes can load cross-domain content but the parent page cannot access the iframe’s contents unless explicitly allowed. In short, by default the browser isolates resources by origin.

When a web app needs to share data across domains (a common case with modern APIs and microservices), the Same-Origin Policy becomes too restrictive. This is where CORS comes in: it provides a standardized way for servers to relax SOP safely. CORS allows a server to say, “Yes, I trust requests coming from this other origin,” by sending special HTTP headers along with its responses. The browser then honors these headers and lets the cross-origin request succeed.

Key Idea: CORS adds flexibility to SOP. It does not override SOP silently – it requires explicit server opt-in. In fact, CORS only works if you trust the browser’s enforcement; a malicious client or non-browser app can ignore CORS headers entirely. In practice, however, web apps rely on browsers to enforce CORS as a security convention.

How CORS Works: Requests, Preflights, and Headers

CORS works through an exchange of HTTP headers between the browser and server. Here’s the typical flow:

Cross-Origin Request Initiation: When JavaScript (e.g. via fetch() or XMLHttpRequest) on a page at origin A tries to request a resource at origin B, the browser automatically adds an Origin header to the request with the page’s origin (scheme + host + port). For example:

Origin: https://domain-a.com
GET /data.json HTTP/1.1
Host: domain-b.com

  1. Server Response with CORS Headers: The server at origin B checks this Origin value. If the server is willing to allow that domain to access its resource, it includes the header Access-Control-Allow-Origin (ACAO) in the response. The ACAO header can either be a specific allowed origin or * (meaning any origin). For example:

    HTTP/1.1 200 OK

Access-Control-Allow-Origin: https://domain-a.com
Content-Type: application/json
… (JSON data) …

  1.  If the Access-Control-Allow-Origin matches the requesting origin, the browser will allow the web app on domain-a.com to read the response. Otherwise, the browser blocks the response (the JavaScript call fails with a CORS error). Importantly, if no ACAO header is present in the response to a cross-origin request, the browser will block access even if the request reached the server.
    • Wildcard Origin (*): Setting Access-Control-Allow-Origin: * lets any domain access the resource. For example, if the server replies with ACAO *, that response is accessible by scripts on any site. This is often used for public APIs. However, note: * cannot be used if the request includes credentials (cookies or HTTP auth) – in that case the server must echo back the requesting origin specifically.
    • Vary Header: When allowing specific origins dynamically (e.g. echoing the Origin header back), it’s good practice to include Vary: Origin so that caches treat responses correctly for different origins.
  2. Other CORS Response Headers: In addition to Access-Control-Allow-Origin, servers can use other ACA-* headers to fine-tune permissions:

    • Access-Control-Allow-Methods: Lists HTTP methods that are allowed (e.g. GET, POST, OPTIONS). This is sent in response to a preflight (described below) to indicate which methods may be used.
    • Access-Control-Allow-Headers: Lists which request headers (especially custom ones) are allowed. Sent in response to a preflight to show which headers the actual request can include.
    • Access-Control-Allow-Credentials: If this is set to true, it tells the browser that the server permits credentials (cookies, HTTP auth) in the request. Without it, the browser will not send cookies or will ignore them in the response.
    • Access-Control-Max-Age: (Optional) Indicates how long (in seconds) the results of a preflight check can be cached by the browser, to avoid repeating it too often.
    • Access-Control-Expose-Headers: (Optional) Tells the browser which response headers can be read by JavaScript (beyond the simple ones).

For example, a successful preflight response might include:
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

  1.  This tells the browser that https://foo.example is allowed, that the methods POST and GET may be used in the actual request, and that the headers X-PINGOTHER and Content-Type can be included. The Max-Age indicates the browser can reuse this decision for the next 24 hours without another preflight.

Simple Requests vs. Preflighted Requests

Not all cross-origin requests use a preflight. CORS distinguishes “simple” requests (which are sent directly) from “preflighted” requests (which trigger an initial OPTIONS check). A simple request is one that meets all of the following criteria:

  • Methods: Only GET, HEAD, or POST.
  • Request Headers: Apart from automatically-sent headers (like User-Agent), only certain “CORS-safelisted” headers can be added manually. Typical safe headers include Accept, Accept-Language, Content-Language, and Content-Type.

Content-Type (if POST): If using POST, the Content-Type (MIME type) must be one of application/x-www-form-urlencoded, multipart/form-data, or text/plain.

If a request meets these rules, the browser considers it “safe” (comparable to submitting an HTML form) and does not send a preflight OPTIONS request. It simply sends the GET/POST directly with the Origin header, and the server must still reply with the appropriate ACAO header for the response to be exposed to the script. For example, a simple fetch(‘https://api.example.com/data’) GET will include Origin: https://myapp.com. The server just needs Access-Control-Allow-Origin: https://myapp.com (or *) in its response. If that’s present, the JavaScript .then() handlers will execute on the response.

A preflighted request is any cross-origin request that does not qualify as simple. For instance, using methods other than GET/HEAD/POST (like PUT or DELETE), sending a POST with a JSON payload (Content-Type: application/json), or adding custom headers like X-My-Header all trigger a preflight. When a preflight is needed, the browser automatically sends an OPTIONS request first, without cookies/credentials, to check if the real request is allowed. The preflight includes:

  • Access-Control-Request-Method: the HTTP method of the upcoming real request (e.g. POST).
  • Access-Control-Request-Headers: a list of any custom headers that will be sent (e.g. X-Custom-Header).

The server must respond to this OPTIONS request with its CORS policy: it should include Access-Control-Allow-Methods listing allowed methods (including the requested one) and Access-Control-Allow-Headers listing allowed headers (if any), along with Access-Control-Allow-Origin. For example:

OPTIONS /data HTTP/1.1

Host: api.example.com

Origin: https://myapp.com

Access-Control-Request-Method: DELETE

Access-Control-Request-Headers: X-PINGOTHER, Content-Type

 

HTTP/1.1 204 No Content

Access-Control-Allow-Origin: https://myapp.com

Access-Control-Allow-Methods: GET, POST, DELETE

Access-Control-Allow-Headers: X-PINGOTHER, Content-Type

Access-Control-Max-Age: 86400

 

If the server allows the DELETE and those headers, it echoes back in Access-Control-Allow-Methods and …-Headers, as above. The browser then sees the reply (status 204 with the CORS headers) and, finding the conditions met, sends the real DELETE request. If any check fails (e.g. the method or header isn’t listed), the browser does not send the actual request and the fetch/XHR promise is rejected with a CORS error.

Enabling Credentials (Cookies) in CORS

By default, browsers do not send cookies or HTTP authentication in cross-origin requests, even if the server is on the same top-level domain. To include credentials (cookies, TLS client certs, auth headers) in a CORS request, two things must happen:

  • Client Side: The JavaScript request must explicitly opt in. For the Fetch API, set fetch(…, { credentials: ‘include’ }). For XHR, set xhr.withCredentials = true.
  • Server Side: The server must respond with Access-Control-Allow-Credentials: true. This header tells the browser that the server consents to receiving credentials.

The MDN Web Docs note that Access-Control-Allow-Credentials only accepts the literal value true; any other value is treated as missing. If the server sets it to false or omits it, the browser will not allow credentials. Moreover, when allowing credentials, you cannot use the wildcard * for the origin. The server must explicitly echo back the requesting origin. In other words:

If a request includes credentials and the server’s Access-Control-Allow-Origin is *, the browser will reject the response.
For example, if a page on https://app.example.com makes a fetch with credentials: ‘include’ to https://api.example.com, the server’s response must have:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true

and not *. If the server mistakenly sends Access-Control-Allow-Origin: * with a credentialed request, the browser errors out. This ensures cookies or HTTP auth aren’t inadvertently leaked to other sites.

CORS in Practice: Enabling on Servers

To enable CORS, servers must send the appropriate headers shown above. How this is done depends on your technology stack. Here are common examples:

Node.js/Express: Use the cors middleware. Simply npm install cors and then in your Express app:
const express = require(‘express’);
const cors = require(‘cors’);
const app = express();

app.use(cors()); // Enable CORS for all routes, allowing any origin:contentReference[oaicite:41]{index=41}.

// For a specific route:

app.get(‘/products/:id’, cors(), (req, res) => { … });

 This sends Access-Control-Allow-Origin: * by default. To restrict to one origin, pass options:

var corsOptions = { origin: ‘http://example.com’ };

app.get(‘/products/:id’, cors(corsOptions), (req, res) => { … });

 Here only requests from http://example.com will be allowed. The middleware also handles preflight automatically. If you need to explicitly handle OPTIONS (preflight) for certain routes (e.g. for DELETE), you can do:

app.options(‘/products/:id’, cors());  // enable CORS preflight for this route

  •  (Express’s cors middleware documentation has more details.)

Spring Boot (Java): You can annotate controllers or methods with @CrossOrigin. For example:

@RestController

public class MyController {

    @CrossOrigin(origins = “http://localhost:9000”)

    @GetMapping(“/greeting”)

    public String greeting() { … }

}

  •  This adds CORS headers to that endpoint. By default, @CrossOrigin allows all origins and methods (as noted in the Spring guide), but setting origins=”http://localhost:9000″ restricts it to that origin. Spring can also be configured globally via a WebMvcConfigurer bean to apply CORS rules across the app.
  • Other frameworks/servers: Most web frameworks and servers have equivalent settings. For example, in .NET Core you can use services.AddCors() with a policy, in Flask use the flask-cors extension, or configure Apache/Nginx to add the headers. The key is the same: ensure the response includes the right Access-Control-Allow-* headers based on your policy.

Common CORS Interview Questions

Here are some typical CORS questions and concise answers you should know for an interview:

  • What is CORS and why do we need it?
    “CORS stands for Cross-Origin Resource Sharing. It’s a browser-enforced standard that lets servers explicitly allow web pages from other domains to request resources. We need it to relax the Same-Origin Policy (which normally blocks cross-domain requests) in a controlled way.”
    What is the Same-Origin Policy?
    “It’s a browser security rule that an origin (protocol+host+port) can’t read data from a different origin. For example, a script on https://siteA.com can’t normally fetch data from https://siteB.com. SOP prevents malicious sites from accessing private data on another site.”
  • How does CORS work in short?
    “When a cross-origin request is made (e.g. via fetch), the browser sends an Origin header. The server checks this header and, if allowed, includes Access-Control-Allow-Origin: <origin> (or *) in its response. This tells the browser to permit the response to be read by the calling script. For non-simple requests, the browser first does an OPTIONS preflight to ask the server’s permission.”
  • What are simple requests?
    “Simple requests use GET/HEAD/POST with safe headers and content types (e.g. form data or text). They skip the preflight. For such requests, the browser just sends them with Origin and expects Access-Control-Allow-Origin back.”
  • What is a preflight request?
    “A preflight is an automatic OPTIONS request the browser sends before a complex CORS request (like PUT/DELETE or POST with JSON or custom headers). It checks if the server will allow the actual request by inspecting headers like Access-Control-Allow-Methods and Access-Control-Allow-Headers in the response. If the preflight is allowed, the real request proceeds; otherwise it’s blocked.”
  • What headers are involved in CORS?
    “Key request header: Origin (sent by browser). Key response headers:

    • Access-Control-Allow-Origin (which origin is permitted, or *).
    • Access-Control-Allow-Methods (allowed HTTP methods).
      Access-Control-Allow-Headers (allowed custom headers).

    • Access-Control-Allow-Credentials: true (if sending cookies).
    • Access-Control-Max-Age (how long the browser can cache the preflight).
      The browser also sends Access-Control-Request-Method and Access-Control-Request-Headers during preflight.”

  • How do you allow cookies in a cross-origin request?
    “The client must set fetch(…, {credentials: ‘include’}) (or xhr.withCredentials = true), and the server must respond with Access-Control-Allow-Credentials: true. Also, you can’t use * for the origin when allowing credentials; you must echo the request’s origin.”

  • What if there is no Access-Control-Allow-Origin header in the response?
    “The browser will block the response and report a CORS error. You’ll see an error like “No ‘Access-Control-Allow-Origin’ header present on the requested resource.” This means the server didn’t opt-in to CORS, so the browser enforces SOP.”

  • Does CORS protect against CSRF or other attacks?
    “No. CORS is a browser-side access-control mechanism, not an attack-prevention feature for the server. It tells the browser to allow or deny the response, but it doesn’t verify if the request itself is legitimate. As one security blog points out, CORS is not a protection against cross-site request forgery (CSRF) or XSS – it’s simply a way to relax SOP. A poorly-configured CORS policy can even introduce vulnerabilities.”

To fully understand web protocols and boost your technical skills, enroll in our Data Structures and Algorithms

Best Practices and Common Pitfalls

  • Restrict Origins Whenever Possible: Use specific origins rather than *. Wildcards are risky, especially if you use cookies. For credentialed requests, always echo back the specific origin (never *).

  • Handle Preflight Requests: Make sure your server responds to OPTIONS requests on CORS routes. If you see CORS errors for PUT/DELETE, it’s often because OPTIONS isn’t handled. (In Express, for example, app.options(‘*’, cors()) or app.use(cors()) covers this.).
  • Set Vary: Origin if Needed: If you allow multiple domains dynamically by echoing the Origin header, include Vary: Origin so that intermediate caches know responses differ by origin.

  • Check Browser Console for Errors: Browsers silently block disallowed cross-origin responses. The JavaScript only sees a network error. Always look at the console or network tab for detailed CORS error messages.

  • Don’t Treat CORS as a Security Shield: Remember, CORS is enforced by browsers. Non-browser clients (like cURL or Postman) will ignore these headers. Also, a malicious user could write a custom client that spoofs CORS. Thus, CORS should complement, not replace, proper server-side security (authentication, CSRF tokens, input validation, etc.).
  • Be Mindful of Access-Control-Allow-Credentials: If you set credentials: include, be sure to include Access-Control-Allow-Credentials: true in the server response. Forgetting it will cause the browser to drop cookies.
  • Limit Access-Control-Allow-Headers: Only allow the headers you need. For custom headers, list them explicitly in Access-Control-Allow-Headers to avoid overly permissive defaults.
  • Max-Age Caching: Use Access-Control-Max-Age to reduce redundant preflights for the same endpoint, improving performance. But note each browser has its own cap on max-age.

Conclusion and Next Steps

Understanding CORS in depth is crucial for both building modern web apps and answering advanced interview questions. The key takeaways are: CORS = server opt-in to SOP via headers; know the roles of the main Access-Control-Allow-* headers; be able to explain simple vs preflight requests; and recognize the credential/cookie rules. Practice by configuring CORS in your projects (e.g. try the Express and Spring examples above), and troubleshoot any errors by checking your browser’s console and network trace.

For further reading, see our article on Same-Origin Policy which covers the browser security model underlying CORS, and our guide on API Security Best Practices. If you enjoyed this deep dive, share it or leave a comment below, and good luck with your interviews!

References: Authoritative sources and docs have been cited throughout, including Mozilla MDN, PortSwigger Academy, and official framework guides, to ensure accuracy and depth.

FAQs

What is Cross-Origin Resource Sharing (CORS) and why is it used?

CORS is a web security mechanism that allows servers to specify who can access their resources from a different origin. It’s used to relax the browser’s default Same-Origin Policy in a controlled way, enabling safe cross-domain API calls.

The Same-Origin Policy blocks web pages from reading data from a different origin (different protocol, domain, or port). For example, code on https://siteA.com can’t fetch data from https://siteB.com unless CORS headers permit it. SOP is a security feature to isolate sites from each other.

A preflight is an automatic OPTIONS request the browser sends before a cross-origin request that isn’t “simple” (e.g. uses PUT/DELETE or custom headers). The browser checks the response’s Access-Control-Allow-* headers to see if the actual request is allowed. If allowed, the browser proceeds; otherwise it blocks the request.

DSA, High & Low Level System Designs

Buy for 60% OFF
₹25,000.00 ₹9,999.00

Accelerate your Path to a Product based Career

Boost your career or get hired at top product-based companies by joining our expertly crafted courses. Gain practical skills and real-world knowledge to help you succeed.

Reach Out Now

If you have any queries, please fill out this form. We will surely reach out to you.

Contact Email

Reach us at the following email address.

Phone Number

You can reach us by phone as well.

+91-97737 28034

Our Location

Rohini, Sector-3, Delhi-110085

WhatsApp Icon

Master Your Interviews with Our Free Roadmap!

Hi Instagram Fam!
Get a FREE Cheat Sheet on System Design.

Hi LinkedIn Fam!
Get a FREE Cheat Sheet on System Design

Loved Our YouTube Videos? Get a FREE Cheat Sheet on System Design.