CORS Laravel

I want to talk about the problems that I have encountered setting up CORS between a Laravel backend server and a simple Html frontend hosted on a different domain, using the fetch API.

What is CORS

Cross-Origin Resource Sharing (CORS) is a mechanism that uses HTTP headers to tell browsers to give a web application running at one origin, access to selected resources from a different origin. A web application executes a cross-origin HTTP request when it requests a resource that has a different origin (domain, protocol, or port) from its own.

<cite>MDN Web docs</cite>

An example is having a backend server in a domain different from the frontend.

Eg. example.com admin.example.com

The browsers will block all the requests cross-domain and throw an error. This issue is also present in development because the server and frontend must be hosted on different ports (Like localhost:8080 and localhost:8000).

Implementation

The best place to read the implementation of CORS is MDN Web Docs. Most of the setup on the frontend is already done by fetch. The first step in the CORS mechanism is a Preflighted request.

Preflighted request

The Preflighted request is an OPTIONS request that checks if the backend server understands the necessary headers.

The headers are:

Request

I needed to make an Ajax POST request for some resources. I would send and receive data in json format. The preflighted request would look something like this:

GET /data/ HTTP/1.1
Host: admin.example.com
User-Agent: Mozilla/5.0 (...) Gecko/20100101 Firefox
Accept: */*
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://example.com

With the fetch API

fetch.ajax('admin.example.com', {
        method: 'POST',
        mode: 'cors',
        body: JSON.stringify({
            data: 'data',
        });

Laravel API

On the API side on Laravel we need to use a Middleware to intercept the OPTION request and add the necessary headers:

We can do it two ways. Using a custom Middleware created on your own, for simple testing, or the more complete plugin barryvdh/laravel-cors

You can find the implementation of the custom Middleware in this Stack Overflow answer. The code is:

<?php namespace App\Http\Middleware;

use Closure;

class CORS {

    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {

        header("Access-Control-Allow-Origin: *");

        // ALLOW OPTIONS METHOD
        $headers = [
            'Access-Control-Allow-Methods'=> 'POST, GET, OPTIONS, PUT, DELETE',
            'Access-Control-Allow-Headers'=> 'Content-Type, X-Auth-Token, Origin'
        ];
        if($request->getMethod() == "OPTIONS") {
            // The client-side application can set headers allowed in Access-Control-Allow-Headers
            return Response::make('OK', 200, $headers);
        }

        $response = $next($request);
        foreach($headers as $key => $value)
            $response->header($key, $value);
        return $response;
    }
}

Issue

After adding the Middleware I had still issues with the browser blocking the request. The response to the Preflighted Request had a 200 Status Code, but no CORS headers. After some digging, I found out that Laravel returns an html page if you dont specify a return type Accept-Content: application/json in the request and it will result in a 302 redirect to the page which will blocked by default from the browser. So in the fetch function, we need to add the request and response content-type headers:

With the fetch API

fetch.ajax("admin.example.com", {
    method: "POST",
    mode: "cors",
    headers: {
        Accept: "application/json",
        "Content-Type": "application/json"
    },;:
    body: JSON.stringify({
        data: "data"
    })
});

An alternative would be to use a different package like axios that automatically sets the header for the content-type and XMLHttpRequest for Laravel to recognize the request as ajax.

Conclusion

In conclusion, I suggest the use of the plug-in barryvdh/laravel-cors for the best configuration and features.