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:
- Access-Control-Request-Method
- Access-Control-Request-Headers
- Origin
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:
- Access-Control-Allow-Origin
- Access-Control-Allow-Methods
- Access-Control-Allow-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 don’t 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.