7

So I am using Laravel 5.8 as an API to a ReactJS view.

I already created a 'cors' middleware, i registered it on Kernel.php file, and I am using it on the api-routes that I am using. I tested using a GET Request and it worked, but when I test with a POST Request, I get the cors error:

Access to fetch at 'http://localhost:8000/api/posts' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

So i have on my api.php ("/routes/api.php"):

Route::get('/posts', 'PostController@index')->middleware('cors');
Route::post('/posts', 'PostController@store')->middleware('cors');

My cors.php middleware:

<?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', 'GET, POST, PUT, DELETE, OPTIONS')
      ->header('Access-Control-Allow-Headers', 'Content-Type, Accept, Authorization, X-Requested-With, Application');
  }
}

On my Kernel.php ("/app/Http/Kernel.php") I updated the "$routeMiddleware" array with my 'cors' middleware

'cors' => \App\Http\Middleware\Cors::class, 

Now in my React project, my api.js (where I made the code to make the requests):

// get Posts
export const getPosts = () => {
  return fetch('http://localhost:8000/api/posts')
    .then(res => res.json())
    .then(json => console.log(json))
    .catch(err => console.log(err));
}

// create new post
export const createPost = (post) => {

  return fetch('http://localhost:8000/api/posts',
  {
    method: 'post',
    headers: {
      'Accept': 'application/json, text/plain, */*',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(post)
  })
  .then(res => res.json())
  .then(res => console.log(res));
}

I don't understand why everything is working fine when i try the Get request, but when i try the Post Request, i get the CORS error. Someone already had this problem?

1
  • Access-Control-Allow-Origin: * is not accepted by browser, you need to specify the domains allowed. no more jokers * Commented Jul 15, 2019 at 15:33

5 Answers 5

5

So I was having the same problem and spent hours debugging and figuring out what was wrong when I was using my own CORS middlewares in api.php without using the fruitcake one (barryvdh/laravel-cors).

After hours of debugging and frustration I figured out that when you use a middleware in a group, then it doesn't get applied right away.

How laravel matches routes and "applies" middlewares:

When you send a request, laravel reads the api.php and just "registers" all the routes and middlewares and "remembers" them without actually executing them. After it "registers" all of them (reads the whole api.php file), it executes a function where it inputs the path from the URL and the HTTP method that was used in the request and then it begins to find the route that matches the URL and HTTP method and after it finds one, it executes those middlewares that this route is located in and then it executes the controller methods.

So for example with your code when you send a GET request to /api/posts, it matches the resource method index and then executes the middleware cors and therefore it works and returns data from your controller.

Why POST, PUT, DELETE and PATCH don't work with this approach:

When you send a POST, PUT, DELETE or PATCH request to /api/posts, the browser sends an OPTIONS request first, so laravel "registers" all the routes and then it executes the "matching" using the URL and the HTTP method (it is OPTIONS right now).

But there is no route that has a method of OPTIONS and resources don't have an OPTIONS method either, so since there is no route that has an OPTIONS method, laravel doesn't match anything and therefore it does not execute those middlewares where you eventually handle OPTIONS methods.

api.php example

Route::post('/posts', 'PostController@store')->middleware('cors');

The matching function in Laravel:

The "matching" function is called findRoute and it is located in vendor/laravel/framework/src/Illuminate/Routing/Router.php.

/**
 * Find the route matching a given request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @return \Illuminate\Routing\Route
 */
protected function findRoute($request)
{
    $this->current = $route = $this->routes->match($request);

    $this->container->instance(Route::class, $route);

    return $route;
}

When you log $route with error_log(json_encode($route), 0);, then make a GET request and then look in error logs, you can see the succesful "match" and that it applied the cors controller: {"uri":"api\/posts","methods":["GET","HEAD"],"action":{"middleware":["cors"],"uses":"App\\Http\\Controllers\\PostController@index","controller":"App\\Http\\Controllers\\PostController@index","namespace":null,"prefix":"api","where":[]},"isFallback":false,"controller":null,"defaults":[],"wheres":[],"parameters":[],"parameterNames":[],"computedMiddleware":null,"compiled":{}}

But when you send a POST request, this happens: {"uri":"api\/posts","methods":["OPTIONS"],"action":{"uses":{}},"isFallback":false,"controller":null,"defaults":[],"wheres":[],"parameters":[],"parameterNames":[],"computedMiddleware":null,"compiled":{}}

There you can see that actually OPTIONS method was sent (because browser sends an OPTIONS method first) and nothing got matched and no middleware was applied and therefore the PUT request failed with a CORS error (Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.)

The summary and solution:

  • Middlewares in $routeMiddleware array get applied after Laravel succefully matches a route with the path and the HTTP method because different routes can have different middlwares.
  • Middlewares in $middleware array (global middlewares) get applied before Laravel begins registering and matching routes.

To solve it, you have to use a global middleware in $middleware array, that handles the OPTIONS method. You can just use the Fruitcake one that handles it and then you can use your own CORS middlewares in api.php that can set different headers to your liking (for example different Allowed Origins for different routes/groups/prefixes.

Sign up to request clarification or add additional context in comments.

Comments

3

change your middleware to this

<?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)
  {
    $domain = parse_url($_SERVER['HTTP_REFERER']);
    $host = '*';
    if (isset($domain['host'])) {
        $host = $domain['host'];
    }
    return $next($request)
      ->header('Access-Control-Allow-Origin', $host)
      ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
      ->header('Access-Control-Allow-Headers', 'Content-Type, Accept, Authorization,     X-Requested-With, Application');
  }
}

but once in production, you need to limit the allowed hosts by an environment variable.

You can also just use barryvdh/laravel-cors Link here

3 Comments

Thanks for the reply. I tried your solution and it did not worked. But i tried the 'barryvdh/laravel-cors' solution, and the thing is, it is working (as my first 'cors' middleware) when i try the GET Request, but when i try the POST Request i get the same error: "Access to fetch at 'localhost:8000/api/posts' from origin 'localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors'..." This is really weird.
I found something that helped me: github.com/barryvdh/laravel-cors/issues/360 I changed what i was returning from my Controller and strangely, it is working now the Post Request. (Using 'barryvdh/laravel-cors' solution)!
@JulioW. happy to lead you to the right solution for you. you had some echo in your code ?
3

The only thing that solved this issue for me was to put the cors middleware class in the top of $middleware array in Kernel.php

protected $middleware = [
        \App\Http\Middleware\Cors::class,
        \App\Http\Middleware\CheckForMaintenanceMode::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        \App\Http\Middleware\TrustProxies::class,

    ];

here is the cors middleware i am using

<?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)
    {
        $response = $next($request);

        $response->header("Access-Control-Allow-Origin","*");
        $response->header("Access-Control-Allow-Credentials","true");
        $response->header("Access-Control-Max-Age","600");    // cache for 10 minutes

        $response->header("Access-Control-Allow-Methods","POST, GET, OPTIONS, DELETE, PUT"); //Make sure you remove those you do not want to support

        $response->header("Access-Control-Allow-Headers", "Content-Type, Accept, Authorization, X-Requested-With, Application");

        return $response;
    }

}

hope it will help someone someday.

Comments

1

I solved this issue by using FormData instead of JSON.stringfy:

So, I changed:

let data = JSON.stringify({firstname:'John', familyname: 'Doe'});

to:

let data = new FormData();
data.append('firstname','John');
data.append('lastname','Doe');

and the complete code is something like this:

fetch(YOUR_API_URL, {
    method: 'POST',
    body: data
    }).then(
    res =>res.json()
        .then(res => console.log(res))
    ).catch(err => console.log(err));

Comments

-1

Just change the route type to any

Route::any(/*your path goes here*/)->middleware('cors');

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.