CORS (Cross-Origin Resource Sharing)
Overview
Cross-Origin Resource Sharing (CORS) is a security feature implemented by web browsers that restricts web pages from making requests to a different domain than the one that served the web page. This is known as the "Same-Origin Policy" (SOP). CORS allows web servers to specify who can access their resources and how those resources can be accessed, thus enabling controlled sharing of resources between different origins.
Same-Origin Policy (SOP)
Before diving into CORS, it's essential to understand the Same-Origin Policy.
Origin: In web terms, the origin is defined as a combination of the scheme (protocol), host (domain), and port number. For example:
https://example.com:443
(Origin A)http://example.com:80
(Origin B)https://api.example.com:443
(Origin C)
Same-Origin Policy (SOP): By default, web browsers only allow scripts on a web page to access data from the same origin. This means that a web page loaded from
https://example.com
cannot make requests tohttps://api.example.com
unless certain conditions are met, primarily through CORS.
What is CORS?
CORS is a mechanism that allows restricted resources (like fonts, images, or APIs) on a web page to be requested from another domain outside the domain from which the first resource was served. It works by using HTTP headers to inform the browser that the resource can be shared with other origins.
Key Components of CORS
Preflight Requests
For certain types of requests, particularly those that might affect user data or involve complex operations (like
PUT
,DELETE
, or custom headers), the browser sends a preflight request using theOPTIONS
method to the server. This request checks if the server allows the actual request to proceed.
HTTP Headers
Access-Control-Allow-Origin
: Specifies which origin(s) are permitted to access the resource. This can be a specific origin (https://example.com
) or a wildcard (*
) to allow any origin.Access-Control-Allow-Methods
: Lists the HTTP methods (GET
,POST
,PUT
,DELETE
, etc.) allowed when accessing the resource.Access-Control-Allow-Headers
: Indicates which headers can be used in the actual request.Access-Control-Allow-Credentials
: Specifies whether or not the browser should include any credentials (like cookies or HTTP authentication) with requests to the resource.Access-Control-Max-Age
: Specifies how long the results of a preflight request can be cached by the browser.
Simple vs. Non-Simple Requests
Simple Requests: These are requests that meet certain criteria, such as using standard methods like
GET
,POST
, orHEAD
, and do not require preflight checks.Non-Simple Requests: These requests use methods other than
GET
,POST
, orHEAD
, or include custom headers, triggering a preflight request to ensure the server allows the action.
Example Workflow
Simple Request
A browser makes a
GET
request fromhttps://example.com
tohttps://api.example.com/data
.The server at
https://api.example.com
responds with the data and includes anAccess-Control-Allow-Origin
header withhttps://example.com
as the value.The browser allows the page to access the data.
Preflight Request
A browser makes a
PUT
request fromhttps://example.com
tohttps://api.example.com/update
.Before sending the actual request, the browser sends an
OPTIONS
request (preflight) to check if the server allowsPUT
requests fromhttps://example.com
.The server responds with appropriate CORS headers (
Access-Control-Allow-Methods
,Access-Control-Allow-Origin
, etc.).If allowed, the browser sends the actual
PUT
request.
Does it involve both i.e. client side as well as server side?
Yes, CORS (Cross-Origin Resource Sharing) is a mechanism that involves both the browser (client side) and the backend API (server side). Here’s how it works at both ends.
1. Browser (Client Side)
The browser enforces CORS by implementing the Same-Origin Policy (SOP). This policy restricts how a web page loaded from one origin can interact with resources from another origin. The browser is responsible for:
Initiating CORS Requests: When a web application running in the browser attempts to make a cross-origin HTTP request (e.g., using
fetch
,XMLHttpRequest
, or AJAX), the browser automatically includes theOrigin
header in the request to indicate where the request originated from.Handling Preflight Requests: For certain types of requests (e.g., those using methods other than GET/POST, or with custom headers), the browser sends a preflight
OPTIONS
request to the server to determine whether the cross-origin request is allowed. The browser then processes the server's response to decide whether to proceed with the actual request.Enforcing CORS Policy: The browser checks the CORS headers in the server's response to determine whether the response should be made accessible to the client-side JavaScript. If the server does not allow the origin or fails to include the appropriate CORS headers, the browser will block the response and prevent the web page from accessing the data.
2. Backend API (Server Side)
The backend API is responsible for configuring and sending the correct CORS headers in its responses. This configuration tells the browser whether or not to allow the cross-origin request. The server handles CORS by:
Allowing Specific Origins: The server can specify which origins are allowed to access its resources by setting the
Access-Control-Allow-Origin
header. This can be a specific origin (e.g.,https://www.example.com
) or a wildcard (*
) to allow all origins.Specifying Allowed Methods: The server can control which HTTP methods (e.g., GET, POST, PUT, DELETE) are allowed in cross-origin requests by setting the
Access-Control-Allow-Methods
header.Controlling Allowed Headers: The server can specify which headers can be used in the request by setting the
Access-Control-Allow-Headers
header.Handling Preflight Requests: When the server receives a preflight request (an
OPTIONS
request), it responds with the appropriate CORS headers to indicate whether the actual request should be allowed.Allowing Credentials: If the server needs to allow credentials (like cookies or HTTP authentication) in cross-origin requests, it sets the
Access-Control-Allow-Credentials
header totrue
.
CORS in Both Ends
CORS requires coordination between the client (browser) and the server (backend API):
The Browser's Role: Enforces the CORS policy by checking the server's response headers before allowing the web application to access the response data.
The Server's Role: Configures the CORS policy by sending appropriate headers that indicate whether a cross-origin request is allowed.
Example Flow
A browser sends a cross-origin request to a backend API.
The backend API checks if the request is allowed (based on its CORS configuration) and responds with the appropriate headers.
The browser receives the response, checks the CORS headers, and either allows or blocks the request based on the policy.
How CORS Security Protects the System?
About
CORS (Cross-Origin Resource Sharing) is a security feature implemented by web browsers to prevent malicious websites from making unauthorized requests to different origins (i.e., domains, protocols, or ports) than the one from which the browser loaded the initial web page. By enforcing the Same-Origin Policy (SOP), browsers restrict how web pages loaded from one origin can interact with resources from another origin. CORS allows servers to explicitly grant permission to certain origins to access their resources, thereby protecting sensitive data and preventing certain types of attacks, such as Cross-Site Request Forgery (CSRF).
Scenario:
Let's break down how CORS works and how it can protect the system using an example.
Website A: A web application hosted at
https://www.website-a.com
.Website B: Another web application or API hosted at
https://api.website-b.com
(say implemented using a Java backend).
Objective:
Website A wants to make an AJAX call to Website B's API to retrieve some data.
Steps Involved:
Browser Sends Preflight Request (for Non-Simple Requests):
If Website A attempts to send a non-simple HTTP request (e.g., a request with custom headers, methods other than GET or POST, or content types other than
application/x-www-form-urlencoded
,multipart/form-data
, ortext/plain
), the browser first sends a preflight request to Website B.The preflight request is an
OPTIONS
request that asks Website B if it allows cross-origin requests fromhttps://www.website-a.com
.Example of a preflight request:
OPTIONS /api/resource Host: api.website-b.com Origin: https://www.website-a.com Access-Control-Request-Method: POST Access-Control-Request-Headers: X-Custom-Header
Server Response to Preflight Request:
Website B's server checks its CORS policy to determine whether to allow the request from Website A.
If allowed, the server responds with headers indicating that cross-origin requests from
https://www.website-a.com
are permitted.Example of a response:
HTTP/1.1 200 OK Access-Control-Allow-Origin: https://www.website-a.com Access-Control-Allow-Methods: GET, POST, OPTIONS Access-Control-Allow-Headers: X-Custom-Header
If Website B's server does not allow requests from Website A, it will respond with a 403 Forbidden status or not include the CORS headers, and the browser will block the actual request.
Browser Sends Actual Request (if Preflight is Successful):
If the preflight request is successful and Website B grants permission, the browser sends the actual HTTP request to Website B's API.
The browser includes the
Origin
header in this request to inform the server about the request's origin.Example of an actual request:
POST /api/resource Host: api.website-b.com Origin: https://www.website-a.com Content-Type: application/json X-Custom-Header: Value
Server Response to Actual Request:
Website B's server processes the request and responds with the requested data.
The server again includes CORS headers to indicate that the response can be accessed by the origin (
https://www.website-a.com
).Example of a response:
HTTP/1.1 200 OK Access-Control-Allow-Origin: https://www.website-a.com Content-Type: application/json { "data": "Some data" }
Browser Enforces CORS:
The browser checks the response headers to ensure that the
Access-Control-Allow-Origin
header matches the origin of the requesting site.If the headers are valid, the browser allows the JavaScript on Website A to access the response.
If not, the browser blocks the response, and the JavaScript on Website A is unable to access the data.
Security Implications of CORS
1. Preventing Unauthorized Access
CORS ensures that only authorized domains can access resources on Website B. This prevents malicious websites from making unauthorized API calls to Website B and stealing sensitive information.
2. Mitigating CSRF Attacks
CSRF attacks occur when a malicious website causes a user's browser to perform unwanted actions on a different site where the user is authenticated. CORS helps prevent such attacks by enforcing the Same-Origin Policy and ensuring that cross-origin requests are only allowed from trusted sources.
3. Safeguarding Sensitive Data
Without CORS, a malicious script on an attacker’s website could potentially make requests to the Java backend of Website B and access sensitive data like user information, cookies, or tokens. CORS headers prevent such scenarios by restricting access based on origin.
4. Allowing Controlled Access
CORS provides a mechanism to allow controlled access to resources by specifying which origins are allowed, what HTTP methods can be used, and what headers are permissible. This granularity helps protect the system while still enabling legitimate cross-origin requests.
Potential Risks and Mitigations
While CORS is a powerful tool, incorrect configuration can lead to security vulnerabilities.
Allowing All Origins (
Access-Control-Allow-Origin: *
): This can expose the API to any origin, potentially leading to abuse. It’s safer to specify only trusted origins.Credentials in Cross-Origin Requests (
Access-Control-Allow-Credentials: true
): If CORS allows credentials (cookies, HTTP authentication), ensure that theAccess-Control-Allow-Origin
is not set to*
. Always specify the trusted origin explicitly to avoid exposure.Preflight Caching: Preflight requests can be cached by the browser using the
Access-Control-Max-Age
header. Ensure that this value is appropriate for security needs.
How CORS logic can be handled on Server Side?
CORS logic can be handled either in the Java backend code or by an API gateway, depending on the architecture of the application. Here's a breakdown of both approaches:
1. CORS Handled in Java Backend Code
In many simple or traditional monolithic applications, CORS logic is often implemented directly in the backend code, particularly when using frameworks like Spring Boot. This approach is common when:
Direct Interaction: The frontend interacts directly with the backend without passing through an API gateway.
Custom CORS Logic: The application requires specific or customized CORS logic that is tightly coupled with the backend services.
Simpler Deployment: If there is no API gateway in the architecture, handling CORS directly in the backend makes sense.
Pros:
Granular Control: Backend developers have full control over how CORS is configured for each endpoint.
Flexibility: Easier to implement custom CORS logic based on business requirements.
Cons:
Redundant Configuration: In a microservices architecture, we might need to replicate CORS configurations across multiple services.
Maintenance Overhead: Each service needs to be individually updated if CORS policies change.
Example of CORS in Java Backend
When building a Java backend, especially when using frameworks like Spring Boot, handling CORS is a common requirement, particularly for APIs that are consumed by web frontends hosted on different domains. Here's how CORS can be implemented and managed in a Java backend.
1. CORS in Spring Boot
Spring Boot provides multiple ways to handle CORS. The simplest method is using annotations, but more complex configurations can be handled programmatically.
Basic Configuration with Annotations: We can use the @CrossOrigin
annotation at the class or method level in Spring controllers.
@RestController
@CrossOrigin(origins = "http://example.com")
public class MyController {
@GetMapping("/data")
public ResponseEntity<String> getData() {
return ResponseEntity.ok("CORS enabled data");
}
}
origins
: Specifies which origins are allowed to access the resource. We can set it to specific domains, or use*
for all origins.methods
: Allows to specify which HTTP methods are allowed (e.g.,GET
,POST
).allowedHeaders
: Specifies which headers are allowed in the request.exposedHeaders
: Specifies which headers should be exposed to the frontend.
Global Configuration Using WebMvcConfigurer
: For more control, we can configure CORS globally using the WebMvcConfigurer
interface.
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Authorization", "Content-Type")
.exposedHeaders("Authorization")
.allowCredentials(true)
.maxAge(3600);
}
}
addMapping("/**")
: Applies CORS configuration to all endpoints.allowedOrigins("http://example.com")
: Restricts access to only the specified origin.allowedMethods("GET", "POST", "PUT", "DELETE")
: Limits allowed HTTP methods.allowedHeaders("Authorization", "Content-Type")
: Specifies which headers the client can send.exposedHeaders("Authorization")
: Specifies which headers are exposed to the client.allowCredentials(true)
: Enables sending of cookies and credentials.maxAge(3600)
: Caches preflight responses for 1 hour.
2. Advanced CORS Configurations
In some scenarios, we may need more advanced configurations, such as handling CORS at the filter level or integrating CORS with security configurations.
CORS with Spring Security: When using Spring Security, we need to ensure CORS is configured to work seamlessly with security configurations.
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/api/**").authenticated()
.and()
.httpBasic();
}
}
3. Troubleshooting Common CORS Issues
Even with correct configurations, CORS issues might still arise. Common problems include:
Preflight Failures: The browser sends an
OPTIONS
request before the actual request. If the server doesn't handle the preflight request correctly, the actual request will be blocked. Ensure our server is configured to respond toOPTIONS
requests with the appropriate CORS headers.Credentials and CORS: If our frontend needs to send cookies or HTTP authentication with requests, we must set
allowCredentials(true)
on the server andwithCredentials: true
on the frontend. Note that when credentials are allowed, we cannot use*
forallowedOrigins
.Conflicting Configurations: Ensure that CORS settings in Spring Security don’t conflict with CORS settings in our MVC or global configurations.
4. CORS in Microservices Architectures
In a microservices architecture, where different services are deployed across various domains, handling CORS becomes critical. Each service must be configured correctly to allow inter-service communication without CORS issues.
For example, if we have a frontend served from http://frontend.com
and backend microservices running on http://api.service1.com
and http://api.service2.com
, both services need appropriate CORS configurations to allow requests from the frontend.
5. Testing CORS in Java Applications
Testing CORS can be done using tools like Postman, cURL, or by creating test scripts. For instance, we can use cURL to simulate a CORS request:
curl -H "Origin: http://example.com" --verbose http://api.yourservice.com/resource
This command checks whether the server correctly responds with the Access-Control-Allow-Origin
header.
2. CORS Handled by API Gateway
In modern microservices architectures, it is common to offload CORS handling to an API gateway. API gateways like Kong, NGINX, AWS API Gateway, or Zuul can handle CORS at the entry point, simplifying the configuration across multiple services.
Advantages of Handling CORS at the API Gateway:
Centralized Configuration: CORS policies can be defined in one place and apply to all incoming requests, reducing redundancy and potential for error.
Scalability: When multiple microservices are involved, managing CORS in a centralized gateway reduces the complexity of maintaining consistent policies across services.
Security: API gateways often come with advanced security features that can help enforce CORS policies more robustly.
Performance: API gateways can handle CORS preflight requests efficiently, potentially improving performance by caching CORS responses.
Example of CORS in API Gateway (e.g., AWS API Gateway):
In AWS API Gateway, we can enable CORS for an API by configuring the API Gateway to include appropriate CORS headers in the response. This can be done via the AWS Management Console, AWS CLI, or Infrastructure as Code (IaC) tools like Terraform.
{
"gatewayResponses": {
"DEFAULT_4XX": {
"responseParameters": {
"gatewayresponse.header.Access-Control-Allow-Origin": "'*'",
"gatewayresponse.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'",
"gatewayresponse.header.Access-Control-Allow-Methods": "'GET,OPTIONS'"
}
}
}
}
Pros:
Consistency: CORS policies are consistent across all services and environments.
Ease of Management: Easier to update and maintain CORS policies when they are centralized in the gateway.
Reduced Complexity in Backend: Backend services can focus on core logic without worrying about CORS configuration.
Cons:
Less Granular Control: We might lose some flexibility in applying specific CORS policies to individual services or endpoints.
Gateway Dependency: If the gateway goes down, CORS handling is also affected.
When to Choose Which Approach?
Simple Applications: For smaller applications with limited services or when an API gateway is not in use, handling CORS directly in the backend code is often simpler and sufficient.
Microservices or Distributed Systems: In complex architectures with multiple services, handling CORS at the API gateway is usually preferred for centralized management, consistency, and scalability.
Last updated