Understanding Cross-Origin Resource Sharing (CORS)
Today, it is becoming more common to decouple your frontend (usually a JS framework) and your backend (Node.js, PHP, Python, etc.) or even leverage micro-services.
However, web browsers implement security measures that restrict such cross-origin requests by default. This is where Cross-Origin Resource Sharing
(CORS
) comes into play.
Understanding CORS
is crucial for developers building modern web applications, as it allows you to access resources from different origins while maintaining security safely. Let’s dive into the world of CORS
and explore its key concepts.
Fundamentals
The Same-Origin Policy
Before we delve into CORS
, it’s essential to understand the Same-Origin Policy
, which is the foundation of web security.
The Same-Origin Policy
is a critical security mechanism implemented by web browsers. It restricts web pages from making requests to a domain different from the one serving the web page. An origin is considered the same if the protocol, domain, and port all match.
For example:
https://example.com/page1
andhttps://example.com/page2
have the same originhttps://example.com
andhttp://example.com
have different origins (different protocol)https://example.com
andhttps://example.com:3000
have different origins (different port)https://example.com
andhttps://api.example.com
have different origins (different subdomain)
The Same-Origin Policy
is important because it prevents malicious scripts on one page from obtaining access to sensitive data on another page through the Document Object Model
(DOM
).
However, this policy can be too restrictive for legitimate cross-origin requests, which is where CORS
comes in.
What is CORS
?
Cross-Origin Resource Sharing (CORS
) is a mechanism that allows many resources (e.g., fonts, JavaScript, etc.) on a web page to be requested from another domain outside the domain from which the resource originated.
CORS
works by adding new HTTP headers that allow servers to describe which origins are permitted to read that information from a web browser. It’s a way to relax the Same-Origin Policy
in a controlled manner.
For example, if a user visits https://example.com
and that page wants to make an AJAX request to https://api.example.com, it will be blocked by default due to the Same-Origin Policy
. CORS
provides a way for the server at https://api.example.com to say, “I will accept requests from https://example.com.”
CORS
Headers
CORS
is implemented through a series of HTTP headers. The most important ones are:
Access-Control-Allow-Origin
: Specifies which origins can access the resource. It can be a specific origin,"*"
for any origin, or null.Access-Control-Allow-Methods
: Specifies the HTTP methods (GET
,POST
, etc.) allowed when accessing the resource.Access-Control-Allow-Headers
: Indicates which HTTP headers can be used during the actual request.Access-Control-Allow-Credentials
: Indicates whether the response to the request can be exposed when the credentials flag is true.
For example, a server might send these headers:
Access-Control-Allow-Origin: https://example.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type
These headers tell the browser that https://example.com
is allowed to make GET
, POST
, and PUT
requests to this resource and can include the Content-Type
header in its requests.
Understanding these headers is important because they form the core of how CORS
works. As a developer, you must configure your application to send the appropriate headers based on your security requirements.
Simple vs. Preflight Requests
CORS
requests can be divided into two categories: simple requests and preflight requests.
Simple requests don’t trigger a CORS
preflight. They are typically GET
, HEAD
, or POST
requests with only simple headers. The browser sends these directly, and the server can respond with CORS
headers.
Preflight requests are used for more complex HTTP requests. Before sending the actual request, the browser first sends an HTTP OPTIONS
request to the server, asking for permission.
For example, a PUT
request with a custom header would trigger a preflight request:
OPTIONS /resource HTTP/1.1
Origin: https://example.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
The server would then respond with the appropriate CORS
headers, telling the browser whether the actual request is allowed.
Understanding the difference between simple and preflight requests is important because it affects how your server needs to handle CORS
requests and can impact the performance of your application.
CORS
and Security
While CORS
allows for more flexible web applications, it’s important to understand that it’s not a security feature in itself. CORS
doesn’t protect your server from unauthorized access; it tells the browser to allow certain cross-origin
requests.
Proper security measures, such as authentication and authorization, should always be implemented on the server side. CORS
should be configured to allow only the necessary origins, methods, and headers.
For example, instead of using Access-Control-Allow-Origin: *
, which allows any origin, you should specify the exact origins:
Access-Control-Allow-Origin: https://trusted-site.com
Configuration Examples
Symfony (PHP)
In Symfony, you can use the NelmioCorsBundle to handle CORS
.
Here’s an example configuration:
Install the bundle:
composer require nelmio/cors-bundle
Configure
CORS
in yourconfig/packages/nelmio_cors.yaml
:config/packages/nelmio_cors.yamlnelmio_cors: defaults: origin_regex: true allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] allow_headers: ['Content-Type', 'Authorization'] expose_headers: ['Link'] max_age: 3600 paths: '^/api/': allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] allow_headers: ['X-Custom-Auth'] allow_methods: ['POST', 'PUT', 'GET', 'DELETE'] max_age: 3600
Set the
CORS_ALLOW_ORIGIN
environment variable in your.env
file:.envCORS_ALLOW_ORIGIN=^https?://(localhost|example\.com)(:[0-9]+)?$
Next.js (JavaScript)
In Next.js, you can configure CORS
by creating a custom API route.
Here’s an example:
Create a file
pages/api/cors.js
:pages/api/cors.jsimport Cors from 'cors' import initMiddleware from '../../lib/init-middleware' // Initialize the cors middleware const cors = initMiddleware( Cors({ methods: ['GET', 'POST', 'OPTIONS'], }) ) export default async function handler(req, res) { // Run cors await cors(req, res) // Rest of the API logic res.json({ message: 'Hello World' }) }
Create the
lib/init-middleware.js
file:lib/init-middleware.jsexport default function initMiddleware(middleware) { return (req, res) => new Promise((resolve, reject) => { middleware(req, res, (result) => { if (result instanceof Error) { return reject(result) } return resolve(result) }) }) }
Install the
cors
package:npm install cors
This configuration will apply CORS
to the specific API route. You can adjust the options in the Cors()
function call to fit your needs.
Certainly! I’ll add an example for Laravel to the CORS
Configuration Examples section. Here’s the updated section with Laravel included:
Laravel (PHP)
Laravel provides built-in support for CORS
through its middleware. Here’s how you can configure CORS
in a Laravel application:
Laravel 7 and above come with
CORS
support out of the box. For earlier versions, you may need to install thefruitcake/laravel-cors
package:composer require fruitcake/laravel-cors
Publish the
CORS
configuration file:php artisan vendor:publish --tag="cors"
This will create a
config/cors.php
file.Configure
CORS
inconfig/cors.php
:config/cors.php<?php return [ 'paths' => ['api/*'], 'allowed_methods' => ['*'], 'allowed_origins' => ['http://localhost:3000', 'https://example.com'], 'allowed_origins_patterns' => [], 'allowed_headers' => ['*'], 'exposed_headers' => [], 'max_age' => 0, 'supports_credentials' => false, ];
This configuration:
- Applies
CORS
to all routes under/api
- Allows all HTTP methods
- Allows requests from
http://localhost:3000
andhttps://example.com
- Allows all headers
- Doesn’t expose any headers
- Doesn’t set a max age for preflight requests
- Doesn’t allow credentials (cookies, HTTP authentication) in
CORS
requests
- Applies
If you need more fine-grained control, you can create a
CORS
middleware for specific routes. Create a fileapp/Http/Middleware/Cors.php
:app/Http/Middleware/Cors.php<?php namespace App\Http\Middleware; use Closure; class Cors { public function handle($request, Closure $next) { return $next($request) ->header('Access-Control-Allow-Origin', 'https://example.com') ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS') ->header('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, X-Token-Auth, Authorization'); } }
Then, register this middleware in
app/Http/Kernel.php
:app/Http/Kernel.phpprotected $routeMiddleware = [ // ... 'cors' => \App\Http\Middleware\Cors::class, ];
You can now apply this middleware to specific routes or route groups:
Route::middleware(['cors'])->group(function () { Route::get('/api/data', 'DataController@index'); });
This Laravel configuration gives you flexibility in handling CORS
for your entire application or for specific routes. Remember to adjust the allowed origins
, methods
, and headers
according to your application’s needs and security requirements.
Certainly! I’ll add a new section to the article about querying an API protected with CORS
using curl and Postman. Here’s the new section:
Querying CORS-Protected
APIs
While CORS
is primarily a browser security feature, it’s often useful to test APIs directly using tools like curl or Postman. Here’s how you can work with CORS-protected
APIs using these tools:
Using curl
curl doesn’t enforce CORS
restrictions, as it’s not a web browser. However, to simulate a CORS
request or to test your server’s CORS
configuration, you can send the appropriate headers with your request.
Simple GET request:
curl -H "Origin: http://example.com" \ -H "Access-Control-Request-Method: GET" \ -H "Access-Control-Request-Headers: X-Requested-With" \ -X OPTIONS --verbose \ https://api.example.com/data
This sends a preflight
OPTIONS
request. The server’s response will include theCORS
headers if it’s configured correctly.POST request with custom headers:
curl -H "Origin: http://example.com" \ -H "Content-Type: application/json" \ -H "X-Requested-With: XMLHttpRequest" \ -X POST -d '{"key":"value"}' \ https://api.example.com/data
This sends a
POST
request with a custom header, which would typically trigger a preflight request in a browser.
Using Postman
Postman is more user-friendly for testing APIs and provides a GUI for constructing requests.
Setting up a
CORS
request in Postman:- Open a new request in Postman
- Set the HTTP method (
GET
,POST
, etc.) - Enter the API URL
- Go to the “Headers” tab
- Add the following headers:
- Key:
Origin
, Value:http://example.com
- Key:
Access-Control-Request-Method
, Value:POST
(or whatever method you’re using) - Key:
Access-Control-Request-Headers
, Value:Content-Type, X-Requested-With
(or whatever headers you’re using)
- Key:
Sending a preflight request:
- Set the method to
OPTIONS
- Send the request
- Check the response headers for
CORS-related
headers
- Set the method to
Sending the actual request:
- Set the method back to your intended method (
GET
,POST
, etc.) - If it’s a
POST
request, go to the “Body” tab and add your payload - Send the request
- Set the method back to your intended method (
Interpreting the Results
When testing with curl or Postman, pay attention to the response headers. Look for:
Access-Control-Allow-Origin
: Should match yourOrigin
or be'*'
Access-Control-Allow-Methods
: Should include the method you’re usingAccess-Control-Allow-Headers
: Should include any custom headers you’re sending
If these headers are present and correct, it means the server is configured to allow CORS
requests from your specified origin.
Important Notes
Remember that curl and Postman don’t enforce
CORS
policies. They’ll send the request regardless of the server’sCORS
configuration. This is useful for testing but doesn’t reflect how a web browser would behave.If you’re testing an API that requires authentication, make sure to include the necessary authentication headers or tokens in your requests.
Some APIs might have additional security measures beyond
CORS
, such as CSRF tokens. You may need to account for these in your tests.When testing in production environments, be cautious about sending too many requests, as this might trigger rate limiting or security alerts.
By using these tools, you can effectively test and debug CORS
configurations on your APIs, ensuring they’re set up correctly before integrating them into your web applications. This approach can save a lot of time and frustration when dealing with CORS
issues in development.
Platform.sh & Upsun Configuration
When working with Platform.sh or Upsun, you can dynamically set the allowed origins for CORS
based on the $PLATFORM_ROUTES
environment variable. This is particularly useful for handling multiple environments (development, staging, production) with different URLs.
Here’s an example of how you might parse the $PLATFORM_ROUTES
variable and use it to set the allowed origins:
<?php
// Parse the PLATFORM_ROUTES variable
$routes = json_decode(base64_decode(getenv('PLATFORM_ROUTES')), true);
// Extract the domains from the routes
$domains = array_map(function($route) {
return parse_url($route['original_url'], PHP_URL_HOST);
}, $routes);
// Remove duplicates and null values
$allowed_origins = array_filter(array_unique($domains));
// Add any additional origins you always want to allow
$allowed_origins[] = 'localhost';
// Now use $allowed_origins in your CORS configuration
This approach allows you to dynamically set the allowed origins based on your Platform.sh or Upsun routes, ensuring that your CORS
configuration remains correct across all your environments without manual intervention.