This document provides a comprehensive overview of Cross-Origin Resource Sharing (CORS), a critical security mechanism for web applications that enables controlled cross-domain requests.
CORS is a system that uses HTTP headers to tell a browser that it is safe to allow a web page to make requests to a different domain (origin) than the one that served the web page. In simpler terms, it's a way for servers to grant permission to web applications from other origins to access their resources.
Before diving into CORS, it's essential to understand the Same-Origin Policy (SOP). The SOP is a fundamental security feature implemented by web browsers that restricts how a document or script loaded from one origin can interact with a resource from another origin. An origin is defined by the combination of protocol, domain, and port.
http
, https
www.example.com
, api.example.com
80
, 443
, 8080
If any of these components differ between the document's origin and the requested resource's origin, it's considered a cross-origin request. By default, such requests are blocked by the SOP, preventing malicious scripts from reading sensitive data from other sites.
While SOP is crucial for security, it can also be restrictive for legitimate web applications that need to fetch data from different domains, such as:
CORS provides a standardized and secure way to relax the SOP, allowing developers to explicitly permit cross-origin requests when appropriate.
CORS works by introducing a set of HTTP headers that both the client (browser) and the server use to communicate about the origin of the request and the permissions granted.
CORS distinguishes between two types of cross-origin requests:
A request is considered "simple" if it meets all of the following criteria:
GET
, HEAD
, or POST
.POST
requests, the Content-Type
header is one of the following: application/x-www-form-urlencoded
, multipart/form-data
, or text/plain
.For simple requests, the browser sends the request to the server, and the server responds with specific CORS headers. The browser then checks these headers to determine if the request is allowed.
Any request that does not meet the criteria for a "simple request" is considered a "preflighted" request. This includes requests using methods like PUT
, DELETE
, OPTIONS
, PATCH
, or requests with custom headers or certain Content-Type
values.
Before sending the actual request, the browser automatically sends an "OPTIONS" request to the server. This OPTIONS request is called a "preflight request." The purpose of the preflight request is to inform the server about the actual request that the client intends to make. The server then uses CORS headers in its response to indicate whether it permits the actual request.
Several HTTP headers are involved in CORS communication:
Origin
: This header indicates the origin (protocol, domain, port) of the page making the request.
Origin: https://www.example-client.com
Access-Control-Request-Method
(for preflight requests): Indicates the HTTP method that the client will use for the actual request.
Access-Control-Request-Method: PUT
Access-Control-Request-Headers
(for preflight requests): Indicates the custom HTTP headers that the client will use for the actual request.
Access-Control-Request-Headers: X-Requested-With, Content-Type
Access-Control-Allow-Origin
: This is the most critical header. It specifies which origins are allowed to access the resource. If the client's origin is not in this list (or if it's a wildcard `*`), the browser will block the request.
Access-Control-Allow-Origin: https://www.example-client.com
Access-Control-Allow-Origin: *
(Allows any origin)
Access-Control-Allow-Methods
(for preflight responses): Lists the HTTP methods that are allowed for cross-origin requests.
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers
(for preflight responses): Lists the HTTP headers that are allowed for cross-origin requests.
Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With
Access-Control-Allow-Credentials
: If set to true
, this header indicates that the server allows credentials (like cookies or HTTP authentication) to be sent with the cross-origin request. If this header is present and set to true
, Access-Control-Allow-Origin
cannot be a wildcard (`*`).
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers
: By default, browsers only expose a limited set of response headers to the JavaScript code running in the browser. This header allows the server to specify which additional headers should be accessible.
Access-Control-Expose-Headers: Content-Length, X-Custom-Header
Access-Control-Max-Age
(for preflight responses): Indicates how long the results of a preflight request can be cached in seconds. This helps reduce the number of preflight requests the browser needs to make.
Access-Control-Max-Age: 86400
(Cache for 24 hours)
Implementing CORS typically involves configuring your web server or application framework to include the appropriate `Access-Control-*` headers in its responses.
Using the popular cors
middleware for Express:
// Install: npm install cors const express = require('express'); const cors = require('cors'); const app = express(); // Enable CORS for all origins and all methods app.use(cors()); // Or configure specific origins and methods /* const corsOptions = { origin: 'https://www.example-client.com', methods: ['GET', 'POST', 'PUT', 'DELETE'], allowedHeaders: ['Content-Type', 'Authorization'], credentials: true, optionsSuccessStatus: 204 }; app.use(cors(corsOptions)); */ app.get('/api/data', (req, res) => { res.json({ message: 'Data from the server!' }); }); app.listen(3000, () => { console.log('Server listening on port 3000'); });
Configuring CORS in Startup.cs
:
// In ConfigureServices method: services.AddCors(options => { options.AddPolicy("AllowSpecificOrigin", builder => { builder.WithOrigins("https://www.example-client.com") .AllowAnyHeader() .AllowAnyMethod(); }); }); // In Configure method: app.UseCors("AllowSpecificOrigin");
Your client-side JavaScript needs to fetch data from a different domain.
Access-Control-Allow-Origin
configured.
Server-side configuration (simplified):
Your client needs to send custom headers (e.g., for authentication) or use methods like `PUT` or `DELETE`.
Server-side configuration (handling preflight):
When a cross-origin request needs to include credentials, you must set Access-Control-Allow-Credentials: true
on the server and withCredentials: true
in your client-side fetch options.
Access-Control-Allow-Credentials
is true
, you cannot use a wildcard (*
) for Access-Control-Allow-Origin
. You must specify the exact origin.
Server-side configuration:
The most common CORS error message you'll see in the browser console is:
Access to fetch at 'https://api.example.com/...' from origin 'https://your-client-domain.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Or similar messages indicating missing `Access-Control-Allow-Methods`, `Access-Control-Allow-Headers`, etc.
Checklist for troubleshooting:
Origin
header being sent correctly by the browser?Access-Control-Allow-Origin
header that matches the client's origin?OPTIONS
request with the correct Access-Control-Allow-Methods
and Access-Control-Allow-Headers
?Access-Control-Allow-Credentials: true
set on the server, and is the origin explicitly listed (not a wildcard)?While CORS enables cross-domain requests, it's crucial to implement it securely:
Access-Control-Allow-Origin: *
is convenient but can be a security risk if your API contains sensitive data or performs state-changing operations. Specify exact origins whenever feasible.CORS is an indispensable part of modern web development, allowing for flexible and secure communication between different web origins. By understanding the Same-Origin Policy and the role of CORS headers, developers can effectively implement cross-domain requests, enabling richer and more interconnected web applications.