Nathan Loding
Nathan Loding

Reputation: 3235

Angular CORS requests fail to Laravel backend, but preflight look good

In short, I'm posting data with Angular to a Laravel backend. The OPTIONS/preflight request looks good, but the subsequent POST fails saying that Access-Control-Allow-Origin header is missing from the requested resource.

I'm using Laravel 5 with Angular 1.2.26. Some further documentation on the backend middleware can be found here: https://laracasts.com/discuss/channels/requests/laravel-5-cors-headers-with-filters.

Laravel middleware:

public function handle($request, Closure $next)
    {
        return $next($request)->header('Access-Control-Allow-Origin' , 'http://laravel.app:8001')
            ->header('Access-Control-Allow-Credentials', 'true')
            ->header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT, DELETE')
            ->header('Access-Control-Allow-Headers', 'Content-Type, Accept, Authorization, X-Requested-With')
            ->header('Access-Control-Max-Age', '28800');
    }

Angular config - I've tried with various combinations of the commented code, same results each time:

$httpProvider.defaults.useXDomain = true;
    //$httpProvider.defaults.withCredentials = true;
    //delete $httpProvider.defaults.headers.common["X-Requested-With"];
    //$httpProvider.defaults.headers.common["Accept"] = "application/json";
    //$httpProvider.defaults.headers.common["Content-Type"] = "application/json";

Preflight/OPTIONS:

Remote Address:127.0.0.1:8000
Request URL:http://laravel.app:8000/api/v1/authentication/login
Request Method:OPTIONS
Status Code:200 OK

Request Headers
Accept:*/*
Accept-Encoding:gzip, deflate, sdch
Accept-Language:en-US,en;q=0.8
Access-Control-Request-Headers:accept, content-type
Access-Control-Request-Method:POST
Connection:keep-alive
Host:laravel.app:8000
Origin:http://laravel.app:8001
Referer:http://laravel.app:8001/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36

Response Headers
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:Content-Type, Accept, Authorization, X-Requested-With
Access-Control-Allow-Methods:POST, GET, OPTIONS, PUT, DELETE
Access-Control-Allow-Origin:http://laravel.app:8001
Access-Control-Max-Age:28800
Allow:GET,HEAD,POST
Cache-Control:no-cache
Connection:keep-alive
Content-Encoding:gzip
Content-Type:text/html; charset=UTF-8
Date:Mon, 24 Nov 2014 16:01:57 GMT
Server:nginx/1.6.2
Set-Cookie:laravel_session=blahblah; expires=Mon, 24-Nov-2014 18:01:57 GMT; Max-Age=7200; path=/; httponly
Set-Cookie:XSRF-TOKEN=blahblah; expires=Thu, 01-Jan-1970 00:02:00 GMT; Max-Age=-1416844797; path=/; httponly
Transfer-Encoding:chunked

POST:

Remote Address:127.0.0.1:8000
Request URL:http://laravel.app:8000/api/v1/authentication/login
Request Method:POST
Status Code:500 Internal Server Error

Request Headers
Accept:application/json, text/plain, */*
Accept-Encoding:gzip, deflate
Accept-Language:en-US,en;q=0.8
Connection:keep-alive
Content-Length:47
Content-Type:application/json;charset=UTF-8
Host:laravel.app:8000
Origin:http://laravel.app:8001
Referer:http://laravel.app:8001/
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.65 Safari/537.36

Request Payload
{email: "x", password: "x", rememberMe: false}
email: "x"
password: "x"
rememberMe: false

Response Headers
Cache-Control:no-cache
Connection:keep-alive
Content-Type:text/html; charset=UTF-8
Date:Mon, 24 Nov 2014 16:01:57 GMT
Server:nginx/1.6.2
Transfer-Encoding:chunked

Upvotes: 3

Views: 7582

Answers (4)

Deepak Kumar T P
Deepak Kumar T P

Reputation: 1076

I had same problem. In CORS middleware Add the following header.

public function handle($request, Closure $next)
{
    //All the domains you want to whitelist
    $trusted_domains = ["http://localhost:4200", "http://127.0.0.1:4200", "http://localhost:3000", "http://127.0.0.1:3000"];
    if (isset($request->server()['HTTP_ORIGIN'])) {
        $origin = $request->server()['HTTP_ORIGIN'];

        if (in_array($origin, $trusted_domains)) {
            header('Access-Control-Allow-Origin: ' . $origin);
            header('Access-Control-Allow-Headers: Origin, Content-Type, Authorization, X-Auth-Token,x-xsrf-token');
            header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT, DELETE');
        }
    }
    return $next($request);
}

Upvotes: 0

Rafael F.
Rafael F.

Reputation: 116

I had seme problem. If you use middleware to set Headers, but create a new Response, Response::json(), Response::make(), etc. on controller, this new object don't get the headers set by middleware.

Upvotes: 0

richard
richard

Reputation: 1585

After stepping through the VerifyCsrfToken middleware I've established that it was indeed a token mismatch.

The reason is that Angular was not supplying the CSRF token via header or via a parameter in the post. It worked for GET and OPTIONS requests because these do not validate against the token.

So, I looked into Angular and there is documentation on XSRF Protection (see https://docs.angularjs.org/api/ng/service/$http) and lots of discussion out there on how to add the appropriate headers (e.g. https://github.com/angular/angular.js/issues/5122#issuecomment-36157820).

I haven't had a chance to follow any of this up as I have to keep my project moving and my particular use case allows me to get away with disabling VerifyCsrfToken as I only need CORS whilst in development.

But hopefully this will give someone else a starting point for solving this issue more fully.

Upvotes: 3

Nathan Loding
Nathan Loding

Reputation: 3235

I am unsure if this is an issue with Laravel and the VerifyCsrfToken middleware or not, but the root cause of my issue was that the CSRF token validation was failing. When the error was thrown, the new headers were not included. I'm unclear on the order that these middlewares run in, perhaps that's it, but nonetheless, once I removed the VerifyCsrfToken from the middleware stack, everything lit up.

Upvotes: 2

Related Questions