Cross-origin resource sharing (CORS) is a security mechanism that allows a web page to make requests to a different domain than the one that served the web page. CORS works by adding HTTP headers to the response from the server that allows the browser to know that it is safe to allow the request to go through.
CORS ensures that web pages can only request resources from the same origin (port. domain, scheme) unless explicitly permitted by the server hosting those resources. It helps maintain the security and integrity of web applications while allowing controlled access to resources across different origins.
How does Cross-Origin Resource Sharing (CORS) work?
Imagine you’re planning a party (your website) and you want to invite guests (resources) from different neighborhoods (domains). However, you want to make sure your guests won’t cause any trouble or bring unexpected things to your party. That’s where CORS (Cross-Origin Resource Sharing) comes into play.
In the world of web development, browsers have this security feature that prevents a web page in one domain from making requests to a different domain. CORS is like the bouncer at your party checking invitations.
Web browsers implement a security feature known as the “Same-Origin Policy,” which restricts web pages from making requests to a different domain than the one that served the web page. CORS relaxes these security restrictions, allowing controlled access to resources across different origins while still maintaining security.
When your web page makes a cross-origin request (for example, using JavaScript to fetch data from a different website), the browser sends an HTTP request to the target server. The server can include special HTTP headers in its response, indicating which origins are allowed to access its resources.
If the server allows the request (based on the provided headers), the browser lets your web page access the requested resources. If the server doesn’t permit the request, the browser blocks the access, protecting the user’s data and preventing potential security risks.
Simple Requests
Simple requests involve basic stuff like GET
, POST
, and HEAD
methods. If you’re asking for data or sending a bit of info, it’s usually straightforward.
Simple Requests don’t need preflight checks. This is because these requests are considered to be safe and do not pose a security threat to the server. To qualify as a simple request, a request must meet the following criteria:
- Method: The request method must be one of the following HTTP methods: GET, HEAD, POST, PUT, or DELETE.
- Headers: The request headers must be restricted to a list of allowed headers.
- Content type: The request content type must be one of the following: application/x-www-form-urlencoded, multipart/form-data, or text/plain.
If a CORS request meets all of these criteria, then it will be considered a simple request and will not require a preflight check. This can save time and improve performance, as the preflight request is not necessary for simple requests.
How does Simple CORS request work?
You can have content hosted at server.ugacomp.com
and want it to interact with or make requests to content hosted on another domain like example.com
. If it’s a JavaScript application, the code at server.ugacomp.com
to represent this interaction will be as seen below:
const xhr = new XMLHttpRequest();
const url = "https://example.com/resources/public-data/";
xhr.open("GET", url);
xhr.onreadystatechange = someHandler;
xhr.send();
This code implements a simple exchange between the client and the server using CORS headers to handle the privileges.
So, the GET request from the browser will be as seen below:
GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://server.ugacom.com
The server will give you the following Response:
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
[…XML Data…]
The server at server.ugacomp.com
will return an Access-Control-Allow-Origin
header with Access-Control-Allow-Origin: *
, meaning its resources can be accessed by any origin.
Access-Control-Allow-Origin: *
However, If the system admins at https://example.com
wanted to prevent access to their server’s resources to only requests from https://system.ugacomp.com
or any other domain name, then the Access-Control-Allow-Origin
header response will be defined as seen below:
Access-Control-Allow-Origin: https://system.ugacomp.com
This kind of interaction and pattern of the Origin
and Access-Control-Allow-Origin
headers represent the easiest and simplest use of the access control protocol.
Preflight Requests
Imagine you want to invite a special guest from another neighborhood, and this guest has some unique requirements (custom headers or HTTP methods) for attending your party. Before you officially invite them, you send a “preflight” invitation to ask if they’re okay with your party rules.
In technical terms, a preflight request is an extra step that some web browsers take before the actual request. It’s like sending a polite message (an HTTP OPTIONS request) to the server, asking, “Hey, can my website make this specific type of request to your domain?”
If the server responds with the right permissions (via headers like Access-Control-Allow-Origin), the browser knows it’s okay to send the actual request, and your special guest can come to the party.
Preflight requests require extra steps, making them more complex. These requests have their custom headers that don’t include GET or POST as we see we’ve seen in the Simple Requests.
How does Preflight CORS request work?
The instance below generates an XML
for transmission in conjunction with a POST
request. Additionally, a non-conventional HTTP X-PINGOTHER
request header is established. While these headers fall outside the standard specifications of HTTP/1.1
, they commonly prove beneficial for web applications. Given the request employs a Content-Type of text/xml and introduces a customized header, it undergoes a preflight process.
const xhr = new XMLHttpRequest();
xhr.open("POST", "https://bar.other/doc");
xhr.setRequestHeader("X-PINGOTHER", "pingpong");
xhr.setRequestHeader("Content-Type", "text/xml");
xhr.onreadystatechange = handler;
xhr.send("<person><name>Arun</name></person>");
Based on the above code, here is how the full exchange between client and server happens.
OPTIONS /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://system.ugacomp.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://system.ugacomp.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
This code snippet triggers a preflight request using the OPTIONS
method. This request conveys the request parameters to the server, allowing it to assess whether the actual request, including those specific parameters, is acceptable. It’s crucial to remember that the OPTIONS
method is a safe HTTP/1.1
method designed solely for information gathering and cannot alter any resources.
You will also notice that two other request headers on lines 9 and 10 in the code snippet above are sent respectively):
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
These two request headers are necessary for this interaction. For example,
- During a preflight request, the
Access-Control-Request-Method
header informs the server that the actual request, once sent, will utilize the POST method as its HTTP request method. This allows the server to verify whether it can accept such a request and prepare accordingly.
- During a preflight request, the
Access-Control-Request-Headers header
informs the server about the custom headers, likeX-PINGOTHER
andContent-Type
, which will be included in the actual request. This allows the server to pre-emptively assess whether the request can be accepted under those conditions.
In response, the server sends the Access-Control-Allow-Origin
header, specifying https://system.ugacom.com
as the only permitted origin for accessing the resource. Additionally, it includes the Access-Control-Allow-Methods
header, limiting valid HTTP methods for interacting with the resource to POST
and GET
. This header bears resemblance to the Allow
header but operates specifically within the context of access control.
The server further transmits the Access-Control-Allow-Headers
header, declaring “X-PINGOTHER” and “Content-Type” as acceptable headers for inclusion in the actual request. Similar to Access-Control-Allow-Methods
, this header utilizes a comma-separated list to enumerate authorized headers.
Access-Control-Allow-Origin: https://system.ugacomp.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
The server also transmits the Access-Control-Max-Age
header, specifying the duration, in seconds, that the preflight response can be cached before a new preflight request is necessary. This value defaults to 5 seconds, but in this instance, the maximum age is set to 86400 seconds (24 hours). Be mindful that individual browsers possess internal maximum values that supersede the Access-Control-Max-Age
when exceeded.
Once the preflight request is complete, the real request is sent:
To set up CORS in Nginx, you will need to follow the following steps:
POST /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: https://system.ugacomp.com/examples/preflightInvocation.html
Content-Length: 55
Origin: https://foo.example
Pragma: no-cache
Cache-Control: no-cache
<person><name>Arun</name></person>
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://system.ugacomp.com
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain
[Some XML payload]
CORS Use Cases
You can use CORS to handle various issues in the application and these are some of them:
Fix loading issues with Fonts in CSS declaration
If you have a custom font like @font-face
from a different origin and you want to use it in your CSS declaration, you will have to whitelist cross-origin access just for font files. So, what you need is to create a .htaccess
file in the folder where the font files are stored and then add the following lines:
# Allow font assets to be used across domains and subdomains
<FilesMatch "\.(ttf|otf|eot|woff)$">
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "*"
</IfModule>
</FilesMatch>
Fix JavaScript Errors in the Control Panel
If you encounter JavaScript errors within the ExpressionEngine control panel, and upon investigation, discover that specific resources are not loading due to a cross-origin policy issue, you need to add appropriate headers to grant access to those resources. For example;
- An addon named Example is attempting to make an Ajax request to the themes folder. However, due to Cross-Origin Resource Sharing (CORS) restrictions, this request is being blocked.
- The ExpressionEngine control panel operates on a separate domain:
https://admin.example.com.
- The requested themes folder resides on the main website domain:
https://example.com
.
So, in the user/themes/example/ folder you need to create and add a .htaccess file with the following code:
# Allow access to these theme files from https://admin.example.com
<IfModule mod_headers.c>
Header set Access-Control-Allow-Origin "https://admin.example.com"
</IfModule>
It’s important to note that you don’t necessarily need to specify https://example.com
, as browsers will always allow and enable access to resources from the same origin they live on.
Fix loading issues with the Front-end Ajax Endpoint
If you’ve configured a template as an Ajax endpoint, it’s considered a public resource by default and allows access from all origins (represented by the wildcard “*”). This means any website can access and interact with the endpoint.
Two options exist for managing CORS access:
- Server configuration: Similar to the previously used “.htaccess” method, server-side configuration tools modify headers to control access.
- ExpressionEngine template: A dedicated plugin like “Cross-Origin-Headers” can be installed to directly manage CORS headers within the relevant template.
Both methods assume the endpoint URL is https://example.com/ajax/endpoint
.
In the web root’s .htaccess
, add:
# Allow cross-domain access to our Ajax endpoint
<IfModule mod_headers.c>
SetEnvIf Request_URI "/ajax/endpoint" CORS=True
Header set Access-Control-Allow-Origin "*" env=CORS
</IfModule>
The first line defines an environment variable called CORS
, but only for our specified URI. This variable acts as a switch to control CORS behavior. The second line defines the Access-Control-Allow-Origin
header as usual but with the env=CORS
condition. This ensures the header is only set when the CORS
environment variable is explicitly set.
This approach works effectively regardless of whether ExpressionEngine manages the resource and allows the use of regular expressions in URL patterns, similar to how RewriteCond %{REQUEST_URI}
is used with mod_rewrite
.
Using CORS and CDN Caching
Integrating Cross-Origin Resource Sharing (CORS) with your CDN can be accomplished in two straightforward ways:
- Configure your origin server’s HTTP headers
This involves adding the Access-Control-Allow-Origin
header to your server’s response. The value of this header specifies the allowed origin(s) that can access the resource. For example, you can add the HTTP header Access-Control-Allow-Origin for CSS and font caching.
To use CORS for CDN caching in Apache, this sample code can be added to the configuration file:
<IfModule mod_headers.c>
<FilesMatch "\.(ttf|ttc|otf|eot|woff|font.css|css)$">
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
</IfModule>
To activate CORS in Nginx for CDN caching, the following code can be added in its configuration file:
location ~ \.(ttf|ttc|otf|eot|woff|font.css|css)$ {
add_header Access-Control-Allow-Origin "*";
}
- Utilize your CDN provider’s CORS functionality
Many CDNs offer built-in CORS support. By enabling this feature, your CDN will automatically handle the necessary header configuration, saving you time and effort.
RECOMMENDED READING: How to use Cloudflare on Namecheap Domain Pointed to Contabo
Whitelist domains to access resources using CORS
You can implement Server-side configuration, which involves modifying your server’s configuration files to add the Access-Control-Allow-Origin
header to responses for your resources. The specific steps will vary depending on your server software (e.g., Apache, Nginx, Node.js, etc.). Check out the following guides:
- How to enable Cross-Origin Resource Sharing in Apache Server?
- How to enable Cross-Origin Resource Sharing in Nginx server?
What are the benefits of using Cross-Origin Resource Sharing?
CORS provides several benefits, including:
Improved security
Before CORS, browsers enforced the Same-Origin Policy, which restricted web pages from making requests to a different domain than the one that served the web page. While this policy is a fundamental security measure, it can be too restrictive for modern web applications that often need to access resources from multiple domains. CORS relaxes these restrictions by allowing controlled cross-origin requests.
CORS allows servers to specify which domains are permitted to access their resources. This is done through the use of HTTP headers, such as the Access-Control-Allow-Origin
header, which indicates the origins (domains) that are allowed to access the resource. This ensures that only trusted domains can make cross-origin requests.
CORS introduces a set of HTTP headers that servers can use to specify the permissions for cross-origin requests. These headers include:
Access-Control-Allow-Origin
: Specifies the origin(s) that are allowed to access the resource. It can be a specific origin, a comma-separated list of origins, or a wildcard (*
) indicating that any origin is allowed.
Access-Control-Allow-Methods
: Specifies the HTTP methods (e.g., GET, POST) that are allowed when accessing the resource.
Access-Control-Allow-Headers
: Specifies the HTTP headers that are allowed when making the actual request.
By default, cross-origin requests do not include credentials (such as cookies or HTTP authentication) for security reasons. However, if a server allows credentials, the Access-Control-Allow-Credentials
header is used to indicate this. It helps prevent unauthorized access to sensitive information.
For certain types of requests (such as those with custom headers or using certain methods), browsers may send a preflight request (an HTTP OPTIONS request) to the server to check whether the actual request is safe to send. The server responds with appropriate CORS headers to indicate whether the actual request can proceed.
RECOMMENDED READING: How to Secure HLS & DASH Streams in Ant Media Server?
Enhanced functionality:
CORS allows web pages to make requests to a different domain, enabling web applications to access data from APIs hosted on different servers. This is essential for building rich, client-side web applications that can interact with various data sources and services.
CORS enables web pages to integrate with third-party services, such as social media APIs, payment gateways, or other external resources. This is crucial for creating feature-rich applications that can leverage diverse functionalities.
CORS facilitates communication between different origins, allowing web pages from one domain to send asynchronous HTTP requests (e.g., XMLHttpRequest or Fetch API) to another domain. This is particularly useful for scenarios where data needs to be fetched or exchanged between different parts of a web application.
In a microservices architecture, various services may be hosted on different servers with distinct domains. CORS enables seamless communication between these services from the client side, enabling a more modular and scalable approach to building web applications.
CORS enables web pages to fetch resources, such as images, fonts, or stylesheets, from different domains. This allows for improved performance and better user experience by leveraging content delivery networks (CDNs) and other distributed systems.
Single Page Applications (SPAs) often need to load content dynamically without refreshing the entire page. CORS facilitates such dynamic loading of content from different sources, contributing to a smoother and more interactive user experience.
What are the challenges of using Cross-origin Resource Sharing (CORS)?
CORS can also present some challenges, including:
- increased complexity: CORS can add complexity to web applications, as developers need to be aware of the CORS rules and how to implement them.
- Potential performance issues: CORS can lead to performance issues, as browsers need to make additional requests to check the Origin header.
- Limited control for developers: Developers have limited control over how CORS is implemented on their servers.