CORS support for localhost:3000 in Laravel 7

Viewed 4508

I made sure to look up the web for possible solutions to the issue before posting here.

I am using Nuxt SSR for the front-end, and Laravel 7 as the backend API which now has native CORS implementation. My front-end app is running at http://localhost:3030 and the api at http://gaminghub.test.

This is what I have in the cors.php that comes with the latest version of laravel:

<?php

return [

    /*
    |--------------------------------------------------------------------------
    | Cross-Origin Resource Sharing (CORS) Configuration
    |--------------------------------------------------------------------------
    |
    | Here you may configure your settings for cross-origin resource sharing
    | or "CORS". This determines what cross-origin operations may execute
    | in web browsers. You are free to adjust these settings as needed.
    |
    | To learn more: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
    |
    */

    'paths' => ['api/*'],

    'allowed_methods' => ['*'],

    'allowed_origins' => ['*'],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => false,

    'max_age' => false,

    'supports_credentials' => true,

];

Using postman, this is what's returned when sending get request to http://gaminghub.test/api/products?category=play-card:

{
    "data": [
        {
            "id": 1,
            "name": "Xiaomi",
            "price": 2000000,
            "description": "Best value for money",
            "slug": "xiaomi"
        }
    ],
    "links": {
        "first": "http://gaminghub.test/api/products?page=1",
        "last": "http://gaminghub.test/api/products?page=2",
        "prev": null,
        "next": "http://gaminghub.test/api/products?page=2"
    },
    "meta": {
        "current_page": 1,
        "from": 1,
        "last_page": 2,
        "path": "http://gaminghub.test/api/products",
        "per_page": 1,
        "to": 1,
        "total": 2
    }
}

nuxt.config.js includes:

  /*
   ** Axios module configuration
   ** See https://axios.nuxtjs.org/options
   */
  axios: {
    baseURL: "http://gaminghub.test/api",
    credentials: true
  },

product.vue contains the following to retrieve the data:

<script>
export default {
    data() {
    return {
      product: null
    }
  },

  async asyncData({params, app}) {
    let res = await app.$axios.$get(`products/?category=${params.slug}`)

    return {
      product: res.data
    }
  },
}
</script>

So, basically, when I click on each category they send a get request to api/products?category=clicked-category-slug which results in the following error in console:

Access to XMLHttpRequest at 'http://gaminghub.test/api/products/?category=play-card' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.

The network request header contains the following:

Request Headers Provisional headers are shown Accept: application/json, text/plain, */* Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOlwvXC9nYW1pbmdodWIudGVzdFwvYXBpXC9hdXRoXC9sb2dpbiIsImlhdCI6MTU4MzU3MjQ4MiwiZXhwIjoxNTgzNTc2MDgyLCJuYmYiOjE1ODM1NzI0ODIsImp0aSI6ImdYY1Q3WGRSVnFXdUpocFIiLCJzdWIiOjMsInBydiI6Ijg3ZTBhZjFlZjlmZDE1ODEyZmRlYzk3MTUzYTE0ZTBiMDQ3NTQ2YWEifQ.t4JLsLLE6WIFZp67hbafZl8YTLlzN2WyQVw11mETMNQ Referer: http://localhost:3000/categories/play-card User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36

The error goes away and expected data is returned when I reload the browser. Nuxt is running in universal ssr mode.

I need some guidance in understanding what could be causing this issue. I must be missing something

2 Answers

The error "Redirect is not allowed for a preflight request." happen usually when the server send a "Location" response header to the client. The browser don't like that and thus throw an error. So your laravel controller at the url /api/products?category=play-card may send a Location header, I'm not sure. What you can do to debug this issue for example is inspecting the headers sent to the client with Postman when you request this url.

Hope this will help you.

PS: I'm a beginner in answering questions here so it might be not the right kind of answers.

This is how I got mine working with react 16 and laravel 7

inside my Controller I just used the welcomeController as an example stored in app\http

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;

class WelcomeController extends Controller
{
    public function show() {
        return view('welcome');
    }

    public function post(Request $request) {
        // extra data that may come from a database 
        // or a token or whatever your external component needs
        $post = [
            'title' => 'my title',
            'content' => 'Lorem, ipsum dolor sit amet consectetur adipisicing elit. Hic velit, harum neque adipisci praesentium quo voluptate laudantium ipsam optio provident accusamus modi nobis, facere atque iure perferendis corporis? Quam, distinctio.'
        ];
        $response = array_merge($request->all(), $post);
        return json_encode($response);
    }
}

inside /app/http/Middleware

add file Cors.php. Add the below code

<?php
namespace App\Http\Middleware;
use Closure;
class Cors
{
  public function handle($request, Closure $next)
  {
    return $next($request)
      ->header('Access-Control-Allow-Origin', 'http://localhost:3000')
      ->header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS')
      ->header('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, X-Token-Auth, Authorization');
  }
}

Next inside ./routes build your api out like this

// the order or the middleware matter so make sure that
// cors middleware is first before the api middleware 
Route::middleware('cors:api')->post('/post', 'WelcomeController@post');

Inside of app/config/cors.php, leave this file as is

[
    'paths' => ['api/*'],

    'allowed_methods' => ['*'],

    'allowed_origins' => ['*'],

    'allowed_origins_patterns' => [],

    'allowed_headers' => ['*'],

    'exposed_headers' => [],

    'max_age' => 0,

    'supports_credentials' => false,

];

inside app/Http/Kernel.php in the protected $routeMiddleware array append a new key/value pair, see example below:

 protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
    'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
    'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
    'can' => \Illuminate\Auth\Middleware\Authorize::class,
    'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
    'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
    'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
    'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
    'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
    'cors' => \App\Http\Middleware\Cors::class, // add route middleware
];

Then inside your UI you can test that come data is pulling through, im using react but imm using it with the native javaScript Fetch api to make a post. heres the component below that makes the call and renders the information to the DOM

import React, { Component } from 'react';

class ConnectToApi extends Component {
  
  state = {
    data: []
  }

  componentDidMount() {
    this.handleGetData();
  }

  handleGetData = async () => {
    fetch('http://laravel-api.local/api/post', {
      method: 'post',
      mode: 'cors',
      headers: new Headers({
        'Accept': 'application/json',
        'Content-Type': 'application/json',
      }),
      body: JSON.stringify({
        name: 'John',
        surname: 'Doe',
      })
    })
    .then(response => response.json())
    .then(data => {
      const state = Object.assign({}, this.state)
      state.data.push(data);
      this.setState(state);      
    })
    .catch(error => console.log(error));
  }

  render() {
    console.log(this.state.data);
    
    return <div>
      <header>
      <h1>
        data
      </h1>
    </header>
    <p>Modified data</p>
    {this.state.data.length === 0 && <p>loading data...</p>}
    {
      this.state.data.length > 0  && this.state.data.map((value, index) => {
        return (
          <div key={index}>
            <p>name: {value.name}</p>
            <p>surname: {value.surname}</p>
            <p>title: {value.title}</p>
            <p>Content <br/><br/> {value.content}</p>
          </div>
        )
      })
    }
    </div>
  }
}
export default ConnectToApi;

and then in app.js theres just

import React from 'react';
import logo from './logo.svg';
import './App.css';
import ConnectToApi from './connectToApi'

function App() {
  return (
    <div className="App">
      <ConnectToApi />
    </div>
  );
}

export default App;
Related