Table of Contents
Introduction to CORS
Cross-Origin Resource Sharing (CORS) is a protocol that allows scripts running in a browser client to interact with resources from other origins. This is useful because, due to the same-origin policy enforced by XMLHttpRequest and fetch, JavaScript can only make calls to URLs that share the same origin as the script’s current location. For example, if a JavaScript app attempts to make an AJAX request to an API operating on a different domain, the same-origin policy prevents it from doing so.
Let’s first look at Same-Origin Policy
To further understand CORS, we need to understand the concept of Same-Origin Policy. This policy is a key security mechanism adopted by web browsers to prevent malicious scripts from accessing sensitive data across many domains. This policy allows a web page to only send requests to the same domain (origin) from which it was delivered.
Breaking down the parts of a URL
URLs, or Uniform Resource Locators, are the web addresses guiding you to specific internet pages. Knowing the structure of a URL can enhance your web navigation and website management skills.
URL structure refers to the organization of a URL, which typically follows this format: scheme://subdomain.domain.tld/path?query. A well-structured URL is easy to read and understand for both users and search engines.
Let’s consider below URL:
http://udana.net/about
This uses the scheme http
, the domain udana.net
, and the port number 80
.
The following table shows how the same-origin policy will be applied if content at the above URL tries to access other origins:
URL Accessed | Access permitted? |
---|---|
http://udana.net/about/ | Yes: same scheme, domain, and port |
http://udana.net/about2/ | Yes: same scheme, domain, and port |
https://udana.net/about/ | No: different scheme and port |
http://en.udana.net/about/ | No: different domain |
http://www.udana.net/about/ | No: different domain |
http://udana.net:8080/about/ | No: different port |
Why is the same-origin policy necessary?
When a browser sends an HTTP request from one origin to another, it includes any cookies relevant to the other domain, such as authentication session cookies. This indicates that the response will be created during the user’s session and will include any relevant user-specific information. Without the same-origin policy, if you visited a malicious website, it might read your Gmail emails, Facebook private messages, and so on.
The same-origin policy manages JavaScript code’s access to content loaded across domains. Cross-origin loading of page resources is often allowed. The same-origin policy supports embedding images, videos, and JavaScript using the ,
Due to legacy needs, the same-origin restriction is somewhat relaxed when dealing with cookies, therefore they are frequently accessible from all subdomains of a site, even if each subdomain is technically a distinct origin. You can reduce this danger by utilizing the HttpOnly cookie setting.
Document.domain allows you to relax the same-origin policy. This unique attribute allows you to relax same-origin policies for a certain domain, but only if it is part of your FQDN (fully qualified domain name). For example, suppose you have the domain marketing.example.com and want to read the content of that site on example.com. To do so, both domains must set document.domain to example.com. The same-origin policy will then allow access between the two domains, notwithstanding their distinct origins. Previously, it was possible to set document.domain to a TLD such as com, allowing access between any domains on the same TLD, but contemporary browsers now disallow this.
CORS – Why Is It Needed?
While the Same-Origin Policy is critical for security, it may be overly restrictive for acceptable use cases. This is where cross-origin resource sharing (CORS) comes in, offering a controlled approach to loosen these constraints and permit particular cross-origin requests.
Typically, a script executing in the user’s browser requires access solely to resources from the same origin, such as API calls to the backend that initially delivered the JavaScript code. The inability of JavaScript to typically access resources from different origins is beneficial for security.
In this context, “other origins” means the URL being accessed differs from the location that the JavaScript is running from, by having:
- a different scheme (HTTP or HTTPS)
- a different domain
- a different port
Nonetheless, there are valid circumstances in which cross-origin resource sharing is advantageous or essential. For instance, if you are operating a React/Vue single-page application that communicates with an API backend hosted on a separate domain.
Web fonts like Google fonts also rely on cross-origin resource sharing to work
Cross-origin resource sharing (CORS) plays a vital role in modern web development for several reasons:
- API Consumption: Numerous web apps depend on external APIs to deliver functionalities such as meteorological updates, social network integration, or payment processing. CORS facilitates secure access to these APIs for applications.
- Enabling Resource Sharing:In modern microservices architecture, several components of an application may be deployed on various domains. CORS facilitates smooth communication among different components, hence improving functionality and user experience.
- Security Enhancement: CORS mitigates several limitations of the Same-Origin Policy, although in a regulated fashion. It enables developers to choose specific domains permitted to access their resources, ensuring a robust security framework.
- Compliance with Modern Web Standards:As web applications become more complex and distributed, CORS has become an essential part of web standards, ensuring interoperability and security across the web.
How CORS works?
Cross-origin resource sharing (CORS) operates through a series of HTTP headers that are exchanged between the browser and the server. Let’s break down the key components.
Types of CORS Requests
Cross-origin resource sharing (CORS) requests can be classified primarily into two types: simple requests and preflight requests. Each type has specific characteristics and serves different purposes within the CORS framework.
- Simple Requests
Simple requests are certain kinds of requests that are considered safe enough not to require explicit CORS preflight approval.
Use of HTTP methods: GET, POST, or HEAD.
Only specific headers are permitted, including Accept, Content-Language, Content-Type with specified values, among a limited selection of others.
Requests do not support special headers or ReadableStream objects.
Examples:
A GET request for a public image or a JSON file from a different domain.
A POST request submitting a form to a server from a different domain, where the Content-Type is application/x-www-form-urlencoded, multipart/form-data, or text/plain.
These requests are considered ‘simple’ because they don’t significantly differ from typical HTTP requests and are unlikely to pose a security risk.
- Preflight Requests
Preflight requests are used for more complex or potentially risky requests, providing an additional layer of security.
Involve HTTP methods other than GET, POST, or HEAD, such as PUT, DELETE, or CONNECT. Use of custom headers or non-standard content types. Requests that might modify data on the server.
The browser requests OPTIONS from the server hosting the cross-origin resource before the actual request is made. This OPTIONS request contains headers indicating the HTTP method and those meant for use in the actual request. The server responds whether these headers and methods are acceptable. This reply has headers such as Access-Control-allow-Methods and Access-Control-allow-Headers. The browser sends the real request if the server’s reply indicates that it is approved.
Example:
A PUT request to update a record in a database hosted on a different domain, where the request includes custom headers like X-My-Custom-Header.
CORS depends on preflight requests, which make sure that the server clearly allows cross-origin requests that are more complicated or could be unsafe before they are made. With this mechanism, web security is kept up while still giving current web apps the flexibility they need.
- Actual Requests
These are the requests that carry the actual data payload after the preflight check (if required) has been completed successfully.
HTTP Headers
When a server is properly setup for cross-origin resource sharing, specific headers will be provided. Their presence can indicate that a request is compliant with CORS. Web browsers utilise these headers to ascertain whether an XMLHttpRequest call should proceed or terminate.
Access-Control-Allow-Origin
The principal header that regulates access to a resource is Access-Control-Allow-Origin, however several other headers can also be configured. This header defines the origins permitted to access the resource.
For example, to allow access from any origin, you can set this header as follows:
Access-Control-Allow-Origin: *
Alternatively, it might be restricted to a certain source:
Access-Control-Allow-Origin: https://udana.net
Access-Control-Allow-Methods
It is used to indicate which HTTP methods are permitted while accessing the resources in response to the cross-origin requests.
Examples:
This directive consists of a list of HTTP request methods separated by a comma.
Access-Control-Allow-Methods: POST, GET, OPTIONS
This directive is a wildcard value to indicate all the requests lacking credentials like HTTP cookies or HTTP authentication information.
Access-Control-Allow-Methods: *
Access-Control-Allow-Headers
The Access-Control-Allow-Headers is a component of the CORS protocol that facilitates cross-origin sharing and is issued in reply to a preflight request. It defines the permissible HTTP headers for the following HTTP request, in addition to those authorised by the CORS protocol.
The asterisk is a wildcard for requests that do not have credentials. It tells the client to allow any supported HTTP header during a preflight request.
Access-Control-Allow-Headers: *
HTTP headers that are whitelisted by CORS are always permitted and are generally excluded from the HTTP Access-Control-Allow-Headers header. These consist of: Accept, Accept-Language, Content-Language, and Content-Type. Additional constraints include a limitation on the length of each value to 128 bytes. Nonetheless, additional constraints are disregarded if the secure HTTP headers are explicitly specified.
Access-Control-Allow-Credentials
The HTTP Access-Control-Allow-Credentials response header indicates that the client may share HTTP responses with code when the HTTP request’s credentials mode is set to include. In this context, credentials may include cookies, authorization headers, or TLS client certificates.
When utilized in a preflight request, it indicates whether the HTTP request may be executed with credentials. HTTP queries lacking a preflight mechanism, such as the HTTP GET method, function differently. In such instances, when an HTTP request is executed with credentials and the corresponding HTTP header is absent from the resource, the HTTP response is disregarded and not delivered.
Access-Control-Allow-Credentials: true
CORS Best Practices and Security Guidelines
Misconfigurations are the primary cause of CORS vulnerabilities. Consequently, prevention is a configuration issue. The subsequent sections provide a description of several effective defenses against CORS attacks.
- It may seem obvious but origins specified in the
Access-Control-Allow-Origin
header should only be sites that are trusted. In particular, dynamically reflecting origins from cross-origin requests without validation is readily exploitable and should be avoided. - Avoid using ’*’ for Access-Control-Allow-Origin in production environments. Instead, specify the exact origins that should have access.
- Avoid using the header
Access-Control-Allow-Origin: null
. Cross-origin resource calls from internal documents and sandboxed requests can specify thenull
origin. CORS headers should be properly defined in respect of trusted origins for private and public servers. - Use the Access-Control-Allow-Credentials header cautiously. Only set it to ‘true’ if you absolutely need to send cookies or HTTP authentication.
- Avoid using wildcards in internal networks. Trusting network configuration alone to protect internal resources is not sufficient when internal browsers can access untrusted external domains.
- Use HTTPS for all cross-origin requests to prevent man-in-the-middle attacks.
- CORS defines browser behaviors and is never a replacement for server-side protection of sensitive data – an attacker can directly forge a request from any trusted origin. Therefore, web servers should continue to apply protections over sensitive data, such as authentication and session management, in addition to properly configured CORS.
- Be cautious when allowing all HTTP methods. Only allow the methods that your API actually needs.
Conclusion
Cross Origin Resource Sharing (CORS) is an important part of current web development because it lets domains safely talk to each other while keeping important security measures in place. You can make web apps that are more powerful, flexible, and safe if you know how CORS works and use it properly.
As you can see, setting up CORS requires thinking about things on both the server side and the client side. On the server side, you can use Apache, Nginx, or IIS. On the client side, you can use React, Angular, or Vue.js. There are easy ways to add CORS to your stack.
Don’t forget that CORS is only one part of computer security. Along with other security steps, such as proper authentication, authorization, and data validation, it should be used.
The goal of this article is to give you a full picture of CORS, from its basic ideas to how it can be used in real life. Now that you know these things, you should be able to use CORS in your web projects and make sure that your apps work well and are safe in the ever-changing world of web development.