25

The dreaded CORS Error:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost/mysite/api/test. (Reason: CORS header 'Access-Control-Allow-Origin' missing).

Laravel route:

$router->group(['prefix' => 'api', 'middleware' => 'cors'], function ($router) {
    $router->get('/test', 'MyController@myMethod');
});

Laravel Cors Middlware:

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, Authorization'
        ];
        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;
    }

Laravel Kernel:

 protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'cors' => \App\Http\Middleware\CORS::class
    ];

Relevant .htaccess:

RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]

Relevant Vue.js:

 new Vue({
        el: '#app',
        data: {
           //data here
        },
        http: {
            headers: {
                "Authorization": "Basic " + "apiKeyHere"
            }
        },
        methods: {
            mymethod: function (e)
            {
                e.preventDefault();
                this.$http.get('http://localhost/mysite/api/test').then(
                        function (response)
                        {
                          //do something
                        }
                )
            }
        }
    });

If I take out the Authorization header option the request works.

I've also tried https://github.com/barryvdh/laravel-cors but still no joy. Any help appreciated!

suncoastkid
  • 2,193
  • 7
  • 26
  • 45

9 Answers9

27

Clearly not the ideal solution but it WORKS. I've added this to the top of my routes.php file:

header('Access-Control-Allow-Origin: *');
header( 'Access-Control-Allow-Headers: Authorization, Content-Type' );

It would be nice to get this working without a hack... alas.

UPDATE: It turned out to be IIS related. I ended up setting the headers in the web.config file and now CORS works without hacking the routes.php file.

<httpProtocol>
    <customHeaders>
       <add name="Access-Control-Allow-Headers" value="Origin, Authorization, X-Requested-With, Content-Type, Accept" />
       <add name="Access-Control-Allow-Methods" value="POST,GET,OPTIONS,PUT,DELETE" />
    </customHeaders>
</httpProtocol>

If you want to restrict access, you can add outbound rules:

      <outboundRules>
          <clear />
                <rule name="AddCrossDomainHeader">
                    <match serverVariable="RESPONSE_Access_Control_Allow_Origin" pattern=".*" />
                    <conditions logicalGrouping="MatchAll" trackAllCaptures="true">
                        <add input="{HTTP_ORIGIN}" pattern="(http(s)?://((.+\.)?somesite\.com|(.+\.)?anothersite\.org))" />
                    </conditions>
                    <action type="Rewrite" value="{C:0}" />
                </rule>
      </outboundRules>
suncoastkid
  • 2,193
  • 7
  • 26
  • 45
  • 2
    where exactly do you put these in your web.config? I'm putting them after but it doesn't work. Also I'm not using IIS, I'm using docker/nginx do I still have to do this? – Hirad Roshandel Apr 05 '17 at 16:07
  • your `pattern` didn't work for me, I used this instead: `(https://right\.example\.com|https://left\.example\.com)` – Serj Sagan Mar 13 '19 at 04:42
18

I solve my problem just adding these line on my routes.php Laravel 5.2 For greater then 5.2 in routes/web.php

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, Authorization');

OR register Cors middleware in global HTTP middleware stack

protected $middleware = [
    \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
    \App\Http\Middleware\CorsMiddleware::class
];
M Arfan
  • 4,384
  • 4
  • 29
  • 46
  • @tam5 yea tests fail. Any suggestion how to fix it properly? – Hirad Roshandel Apr 05 '17 at 20:37
  • 1
    @HiradRoshandel I think what I did to get around this was to wrap the headers in a middleware and then to run tests without it, but I don't remember exactly,, I can get back to you later when I have some time to find what I did – tam5 Apr 05 '17 at 20:42
  • Life saver! I did it with global CORS Middleware. And generally for testing problems I filter by checking `if(!\App::environment('testing')` to avoid them. – KeitelDOG Jan 18 '19 at 19:17
13

Laravel 7 or lower:

Your middleware is ok but you need to register Cors middleware in global HTTP middleware stack.

protected $middleware = [
    \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
    \App\Http\Middleware\CorsMiddleware::class
];

Laravel 8 or higher:

All CORS settings may be configured in your application's config/cors.php configuration file. The OPTIONS requests will automatically be handled by the HandleCors middleware that is included by default in your global middleware stack.

Link to the official documentation

Utwo
  • 401
  • 5
  • 10
6

The issue arises from the preflight request, indeed, but the way of handling requires some additional explanation, when we're talking about Laravel - mainly OPTIONS request is routed (something the other answers rather do PHP way, than Laravel way), so, you have to add this to your routes for it to be successfull:

Route::options('/{any}', function(){ return ''; })->where('any', '.*');

Now, let's cater for all other methods - create CORS middleware:

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)
    {
        return $next($request)
            ->header('Access-Control-Allow-Origin', '*')
            ->header('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, DELETE');
    }
}

And finally, for given route, use that middleware:

Route::put('/test', function(){ echo('test'); })->with('cors');

eithed
  • 3,933
  • 6
  • 40
  • 60
3

You can bypass this one without using any middleware like Barryvdh\Cors for Laravel which was not working properly with JWT AUTH , I have added the following statements in the index.php in Laravel just before the Kernel instantiation

header('Access-Control-Allow-Origin: http://localhost:8001');
header('Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Origin, Content-Type, X-Auth-Token,Authorization');
header('Access-Control-Allow-Credentials: true');

add this one before

$kernel = $app->make(Illuminate\Contracts\Http\Kernel::class);

this should work properly with JWT AUTH also. Kindly note that in Access-Control-Allow-Headers you should include Authorization otherwise your accesstoken will not be allowed with Authorization header therefore JWT AUTH will fail. Happy Coding.

SOUMYA
  • 37
  • 3
2

I have solved my problem easily by adding headers in bootstrap/app.php

header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: *');
header('Access-Control-Allow-Headers: *');
Mannu saraswat
  • 1,061
  • 8
  • 15
1

My solution:

$router->group(['prefix' => 'api', 'middleware' => 'cors'], function ($router){
    $router->options('{param1?}/{param2?}/{param3?}', function () {});
});
Gierappa
  • 31
  • 6
0

This answer is based on this article. barryvdh/laravel-cors middleware library, can be used to fix the problem(Cross-Origin Resource Sharing).

step 1 Install it:

composer require barryvdh/laravel-cors

step 2 Releasing vendor files of the library:

php artisan vendor:publish --provider="Barryvdh\Cors\ServiceProvider"

step 3 The command ran in step 2 will copy a cors.php file to config directory, which looks like this:

return [

/*
|--------------------------------------------------------------------------
| Laravel CORS
|--------------------------------------------------------------------------
|
| allowedOrigins, allowedHeaders and allowedMethods can be set to array('*')
| to accept any value.
|
*/

'supportsCredentials' => false,
'allowedOrigins' => ['*'],// ex: ['abc.com', 'api.abc.com']
'allowedHeaders' => ['*'],
'allowedMethods' => ['*'],// ex: ['GET', 'POST', 'PUT', 'DELETE']
'exposedHeaders' => [],
'maxAge' => 0,

];

For allowedOrigins the value can either be ['*'] which indicated that the origin of request can be from any domain, or an array of specific domains which can be the origins that we will allow to send request to our api, like this ['first.com', 'second.com', 'register.third.com']

and also allowedMethods can either be ['*'] or a list of allowed HTTP verbs for instance ['POST', 'GET']

step 4 Registering the cors middleware. Open app/Http/kernel.php and add the HandleCors class to $routeMiddleware like this:

protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'cors' => \Barryvdh\Cors\HandleCors::class, // add this line to enable cors to your routes
];

step 5 Now you can add the laravel-cors middleware to any route that you want. For instance in Routes/api.php I will do this:

Route::apiResource('category', 'CategoryController')->middleware('cors');
Route::apiResource('product', 'ProductController')->middleware('cors');
Gandalf
  • 2,921
  • 5
  • 31
  • 44
0

Even if it works to put your middleware directly into $middleware instead of $routeMiddleware or to simply define the headers globally in the app.php, it is a horrible decision, because it will expose all endpoints with the new CORS policy, in case you only want to make a part of your API public to third party solutions.

The better solution is to only allow OPTIONS calls to go through on all endpoints and still redirect other HTTP methods through the $routeMiddleware to be able to limit the CORS policy only on a subset of endpoints.

So, build two middlewares:

Cors.php

 <?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)
    {
        return $next($request)
          ->header('Access-Control-Allow-Origin', '*')
          ->header('Access-Control-Allow-Methods', '*')
          ->header('Access-Control-Allow-Headers', '*');
    }
}

OptionsCors.php

<?php

namespace App\Http\Middleware;

use Closure;

class OptionsCors
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
      if($request->isMethod('OPTIONS')) {
        return $next($request)
          ->header('Access-Control-Allow-Origin', '*')
          ->header('Access-Control-Allow-Methods', '*')
          ->header('Access-Control-Allow-Headers', '*');
      }
      return $next($request);
    }
}

app/Http/Kernel.php

<?php 
namespace App\Http
{
    class Kernel extends \Illuminate\Foundation\Http\Kernel
    {
        protected $middleware = [
            // ...
            'App\Http\Middleware\OptionsCors',
        ];
        protected $middlewareGroups = [
            'web' => [
                 // ...
            ], 
            'api' => [
                'App\Http\Middleware\UseApiGuard', 
                'throttle:60,1', 
                'bindings'
            ]
        ];
        protected $routeMiddleware = [
            // ...
            'cors' => 'App\Http\Middleware\Cors', 
        ];
    }

}

Now in your routes you have full control about what endpoints will be exposed with your tightened CORS policy:

Route::namespace('Api')->middleware(['cors'])->group(function () {

    Route::get('api/im_open', 'TestController@ping');

});
Route::namespace('Api')->group(function () {

    Route::get('api/im_closed', 'TestController@ping');

});
Martin Braun
  • 10,906
  • 9
  • 64
  • 105