Handling CORS in a Laravel application
Recently we released laravel-cors. This package can add the necessary CORS headers of your Laravel app. In this post I'd like to give a quick explanation of what CORS is and how you can use the package.
What is CORS
Imagine that all JavaScript code for domain X running in a browser would be able to make http requests to an domain Y. Malious code on domain X would be able to interact with site Y without you knowing. In most circumstances you don't want this. Luckily all major browsers only allow sites to make requests against their own domain. They don't allow JavaScript code to make request against a sites on different domains. This is called the same-origin policy.
But there are some scenarios where you do want to allow that behaviour. Think of an API running on domain X that you want to consume via JavaScript running on domain Y. CORS stands for cross-origin resource sharing. It's a standardized way to legitimately poke some holes in the same-origin policy.
Simple requests
When JavaScript running on domain X performs a HEAD
GET
or certain POST
request (with application/x-www-form-urlencoded
, multipart/form-data
or text/plain
to domain Y the browser will add an Origin
header. The application running on domain Y can use this header to check if the request is permitted. If the server responds with a header Access-Control-Allow-Origin
containing the domain X then the browser will conclude that request was allowed. If the server didn't do that most browsers won't allow the JS on domain X to perform any requests towards domain Y.
All other requests
All requests covered by the previous section will probably only be used to retrieve some data. All other ones such as certain POST
requests, PUT
, PATCH
, DELETE
will probably modify existing data on the server. For those kinds of request the browser will send a preflight request before doing the actualy request.
This preflight request using the OPTIONS
verb. The browser will also send along the Origin header mentioned in the previous section. The server should return a response with the Access-Control-Allow-Origin
, Access-Control-Allow-Methods
and Access-Control-Max-Age headers
set. The Access-Control-Allow-Methods
contains the HTTP verbs that are allowed. The Access-Control-Max-Age
contains the time in seconds that no new preflight request should be sent.
Using spatie/laravel-cors
Our spatie/laravel-cors package can handle verifying and setting all required headers for you. The package can be installed via Composer
composer require spatie/laravel-cors
After that you must register the Cors
middleware
// app/Http/Kernel.php
protected $middleware = [
...
\Spatie\Cors\Cors::class
];
Your application will now allow cross origin request coming from all domains. You probably want to publish the config file and set the allow_origins
key to all domains you will allow requests from. Here's the default content of that config file.
return [
/*
* A cors profile determines which orgins, methods, headers are allowed for
* a given requests. The `DefaultProfile` reads its configuration from this
* config file.
*
* You can easily create your own cors profile.
* More info: https://github.com/spatie/laravel-cors/#creating-your-own-cors-profile
*/
'cors_profile' => Spatie\Cors\CorsProfile\DefaultProfile::class,
/*
* This configuration is used by `DefaultProfile`.
*/
'default_profile' => [
'allow_origins' => [
'*',
],
'allow_methods' => [
'POST',
'GET',
'OPTIONS',
'PUT',
'PATCH',
'DELETE',
],
'allow_headers' => [
'Content-Type',
'X-Auth-Token',
'Origin',
'Authorization',
],
/*
* Preflight request will respond with value for the max age header.
*/
'max_age' => 60 * 60 * 24,
],
];
Using CORS profiles
Imagine you want to specify allowed origins based on the user that is currently logged in. In that case the DefaultProfile
which just reads the config file won't cut it. Fortunately it's very easy to write your own CORS profile. A valid CORS profile is any class that extends Spatie\Cors\DefaultProfile
.
Here's a quick example where it is assumed that you've already added an allowed_domains
column on your user model:
namespace App\Services\Cors;
use Spatie\Cors\DefaultProfile;
class UserBasedCorsProfile extends DefaultProfile;
{
public function allowOrigins(): array
{
return Auth::user()->allowed_domains;
}
}
Don't forget to register your profile in the config file.
// config/cors.php
...
'cors_profile' => App\Services\Cors\UserBasedCorsProfile::class,
...
In the example above we've overwritten the allowOrigins
method, but of course you may choose to override any of the methods present in DefaultProfile
.
In closing
It might appear that this whole CORS business is something that aims to protect your server, but that is not the case. It's primary purpose is to protect the user of the browser by not making any requests to unwanted other domains. Don't see CORS as a mechanism to protect your server.
If you want to know more about our laravel-cors package, go check it out on GitHub. You could also opt to use Barry vd. Heuvel's CORS package. Our package is a modern rewrite of the basic features of Barry's excellent one. We created our own solution because we needed our configuration to be very flexible.
Be sure to also check out the packages our team has previously made. There's probably something there that could be of use in your next project.
What are your thoughts on "Handling CORS in a Laravel application"?