Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

webmaster-lawnstarter's avatar

Laravel 5: CORS Headers with Filters

I have been having a problem getting the CORS issues resolved for my API. There is a package for Laravel 4.1+ on GitHub but that does not work for Laravel 5.

A simple explanation / filter would be ideal. I know there are a couple of people having the same problem.

0 likes
57 replies
ChiragGude's avatar

This is what I do in filters.php (Laravel 4.2)

App::before(function($request)
{
    // Enable CORS 
    // In production, replace * with http://yourdomain.com 
    header("Access-Control-Allow-Origin: *");
    header('Access-Control-Allow-Credentials: true');

    if (Request::getMethod() == "OPTIONS") {
        // The client-side application can set only headers allowed in Access-Control-Allow-Headers
        $headers = [
            'Access-Control-Allow-Methods'=> 'POST, GET, OPTIONS, PUT, DELETE',
            'Access-Control-Allow-Headers'=> 'X-Requested-With, Content-Type, X-Auth-Token, Origin, Authorization'
        ];
        return Response::make('You are connected to the API', 200, $headers);
    }
});
webmaster-lawnstarter's avatar

I wrote the follwing to make the same process workable in L5 but it does not fix the problem:

<?php namespace App\Http\Middleware;

use Closure;
use Illuminate\Contracts\Routing\Middleware;
use Illuminate\Contracts\Routing\ResponseFactory;

class APIResponseHeaderMiddleware implements Middleware {

 /**
  * Handle an incoming request.
  *
  * @param \Illuminate\Http\Request $request
  * @param \Closure $next
  * @return mixed
  */
 public function handle($request, Closure $next)
 { 
  // ALLOW OPTIONS METHOD
  $headers = [
      'Access-Control-Allow-Origin' => '*',
         '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 only 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;
 }

}

Any ideas?

ChiragGude's avatar

Do you get this error?

XMLHttpRequest cannot load [SOME URL HERE]. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8000' is therefore not allowed access. The response had HTTP status code 405. 
1 like
webmaster-lawnstarter's avatar

Yea pretty much. here is the exact one.

XMLHttpRequest cannot load [SOME URL HERE]. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8000' is therefore not allowed access. 
ChiragGude's avatar

You need to set the Access-Control-Allow-Origin header before sending a response. Try this:

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 only 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;
 }
1 like
webmaster-lawnstarter's avatar

This does not work either. Is there something I need to change in the nginx config? Or should I be able to do this only in the laravel code?

ChiragGude's avatar

I was able to get it running with just app code. No server config was needed.

1 like
webmaster-lawnstarter's avatar

The problem lies with the OPTIONS request. The middleware does not seem to kick in when using the annotations on a resource controller.

1 like
webmaster-lawnstarter's avatar

I have made some progress. The middleware does not kick in for the OPTIONS request. I don't know if this is a problem with middlewares or the route annotations.

webmaster-lawnstarter's avatar

I have a temporary fix that does not seem to be a good idea for any production server.

Add the following 2 lines at the top of the [bootstrap/app.php] file

// allow origin
header('Access-Control-Allow-Origin: *');
// add any additional headers you need to support here
header('Access-Control-Allow-Headers: Origin, Content-Type');

I am still looking for the correct way of solving this problem using middlewares or something similar.

3 likes
fhferreira's avatar

I make some tests and this working for me:

https://gist.github.com/fhferreira/dfcd5c56dd9599abf75b

<?php namespace App\Http\Middleware;
use Closure;

use Illuminate\Contracts\Routing\Middleware;
use Illuminate\Http\Response;

class CORS implements Middleware {

 /**
  * Handle an incoming request.
  *
  * @param \Illuminate\Http\Request $request
  * @param \Closure $next
  * @return mixed
  */
 public function handle($request, Closure $next)
 {  
  $content = $next($request);
  return ( new Response($content) )->header('Access-Control-Allow-Origin' , '*')
                                   ->header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT, DELETE')
                                   ->header('Access-Control-Allow-Headers', 'Content-Type, X-Auth-Token, Origin');
 }
}
2 likes
webmaster-lawnstarter's avatar

tldr; does not work

it works (sets the headers) for all request methods (POST, GET, PUT, PATCH) except the OPTIONS method request that gets sent out by the browser cors system (and seems to cause the failure)

Edwill's avatar

Any update on this? @ChiragGude Cors implementation only works for Get, I get errors for other request type. And @fhferreira implementation outputs HTTP/1.0 ... with the JSON.

jonnefoy's avatar

Hey @Edwill, I've updated the @fhferreira gist, take a look It should work as expected (don't forget to register your middleware in Kernel.php)

https://gist.github.com/eweap/1df3b1662a500857133a

<?php namespace App\Http\Middleware;
use Closure;

use Illuminate\Contracts\Routing\Middleware;
use Illuminate\Http\Response;

class CORS implements Middleware {

 /**
  * Handle an incoming request.
  *
  * @param \Illuminate\Http\Request $request
  * @param \Closure $next
  * @return mixed
  */
 public function handle($request, Closure $next)
 {
  return $next($request)->header('Access-Control-Allow-Origin' , '*')
          ->header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS, PUT, DELETE')
          ->header('Access-Control-Allow-Headers', 'Content-Type, Accept, Authorization, X-Requested-With');
 }
}
1 like
nloding's avatar

The middleware posted by @jonnefoy and all the previous ones do not work for me. It doesn't seem to work for the OPTIONS request. Anyone have success yet?

webmaster-lawnstarter's avatar

@nloding

Here is how I am currently working around the issue. Its definitely not an ideal solution since it opens up all routes. I just placed the following lines at the top of the index.php file in the /public directory:

<?php
/**
 * Laravel - A PHP Framework For Web Artisans
 *
 * @package Laravel
 * @author Taylor Otwell <taylorotwell@gmail.com>
 */

// TEMPORARY CORS FIX
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token'); // allow certain headers

/*
|--------------------------------------------------------------------------
| Register The Auto Loader
|--------------------------------------------------------------------------
...
3 likes
nloding's avatar

@JonasWeigert - I tried that but I get the same results. The OPTIONS/preflight CORS request goes through and looks good to me, but the subsequent POST fails stating that the "Access-Control-Allow-Origin" is missing from the requested resource. I'm stumped.

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');
    }

AngularJS config (I've tried all combinations of the commented code being commented/uncommented):

    $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:

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
ConsoleSearchEmulationRendering
nloding's avatar

Doing some further testing and GET requests work with CORS, but POST requests never hit the middleware. No idea why yet ...

nloding's avatar

Another update - if I move the header info to the top of app.php like @JonasWeigert has it, it does work now, with all requests. However, I'd prefer to find a way to make the middleware work and log an issue if needed.

nloding's avatar

@Barryvdh - this wasn't about your package, but I will give it a shot later. I was actually able to figure out my issue. I'm not sure if this is a bug with the framework or not, but the CSRF token validation was failing and that response didn't contain the CORS headers. Once I removed the CSRF token check, using the middleware posted above by @jonnefoy works great. Took far too long to figure out though!

Barryvdh's avatar

It's probably a matter of which Middleware is run first. But if it works that's fine :) The package jut allows some more finegrained control about headers/origins/paths etc with a configuration file. But you could just create that middleware and apply it to some routes.

RobinMalfait's avatar

set it in your nginx config or in your .htacess, that will work :)

webmaster-lawnstarter's avatar

@malfait.robin that is definitely an option but not the end solution. The goal is to have the application set all the correct headers so its server configuration independent. There may be a case where you only want certain routes open (not all of them).

RobinMalfait's avatar

the best way to set it is in your .htaccess file (if you use apache) or in your nginx config like this:

.htaccess

Header add Access-Control-Allow-Origin "*"
Header add Access-Control-Allow-Headers "origin, x-requested-with, content-type"
Header add Access-Control-Allow-Methods "PUT, GET, POST, DELETE, OPTIONS"
location / {

     if ($request_method = 'OPTIONS') {

        add_header 'Access-Control-Allow-Origin' '*';
        
        #
        # Om nom nom cookies
        #

        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        
        #
        # Custom headers and headers various browsers *should* be OK with but aren't
        #

        add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
        
        #
        # Tell client that this pre-flight info is valid for 20 days
        #

        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain charset=UTF-8';
        add_header 'Content-Length' 0;

        return 204;
     }

     if ($request_method = 'POST') {

        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';

     }

     if ($request_method = 'GET') {

        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Credentials' 'true';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';

     }

}

/copyright https://gist.githubusercontent.com/michiel/1064640/raw/b31183bfed8a7ce422f9818c2802a24526972d56/cors-nginx.conf

leamasuero's avatar

Hey @JonasWeigert ,

your approach was quite correct, except for the if statement

if($request->getMethod() == "OPTIONS") {
         // The client-side application can set only headers allowed in Access-Control-Allow-Headers
         return Response::make('OK', 200, $headers);
}

Your OPTIONS response needs to tell your client that he is allowed to proceed. However, you are sending 200 OK.

Try this one:

<?php

namespace App\Http\Middleware;

use Closure;

class APIResponseHeaderMiddleware {

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

        // ALLOW OPTIONS METHOD
        $headers = [
            'Access-Control-Allow-Origin' => '*',
            'Access-Control-Allow-Headers' => 'Origin, Content-Type'
        ];

        if ($request->getMethod() != "OPTIONS") {
            return $next($request);
        }

        $response = $next($request);

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

        return $response;
    }

}

iveybank's avatar

I was able to get it running with just app code. No server config was needed.

Next

Please or to participate in this conversation.