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.