Handling Cross-Origin Resource Sharing (CORS) Requests in Laravel 7

August 14, 2020
Written by
Michael Okoko
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by

By default, browsers implement a same-origin policy that prevents scripts from making HTTP requests across different domains. Cross-Origin Resource Sharing (CORS for short) provides a mechanism through which browsers and server-side applications can agree on requests that are allowed or restricted.

From version 7, the Laravel framework comes with first-party support for sending CORS headers using Middlewares

In this tutorial, we'll be building a simple Vue.js app powered by Laravel to learn about CORS. We will take a deep-dive into the various configuration options needed by Laravel to handle CORS OPTIONS requests and see how some of these options affect our application.

Prerequisites

In order to complete this tutorial, you will need the following:

  • Familiarity with the Laravel framework
  • An existing project with Laravel framework version >= 7.0.
  • Vue CLI installed.

Getting Started  

Create a new folder to hold both the Laravel API and the Vue project, and create the Laravel project with the commands below:

$ mkdir laravel-cors && cd laravel-cors
$ laravel new server

The Laravel project created includes a generic CORS configuration at $APP_FOLDER/config/cors.php which can be fine-tuned to meet your application needs. Here is a sample configuration file generated for a new Laravel application (without the comments).

<?php


return [
    'paths' => ['api/*'],
    'allowed_methods' => ['*'],
    'allowed_origins' => ['*'],
    'allowed_origins_patterns' => [],
    'allowed_headers' => ['*'],
    'exposed_headers' => [],
    'max_age' => 0,
    'supports_credentials' => false,
];

To inspect the API response headers from the Vue frontend, modify the `exposed_headers` array to use the wildcard ` character as shown below:

'exposed_headers' => ['*'],

Next, we will set up our API routes to respond to requests with fake data. Open the api routes file at `routes/api.php`, and replace its content with the code block below.

<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::get('/items', function(Request $request) {
    $data = [
        [
            "id" => 7,
            "name" => "na like this",
            "description" => "",
            "created_at" => "2020-07-26T05:53:00.376501Z",
            "updated_at" => "2020-07-26T05:53:00.376501Z"
        ], [
            "id" => 5,
            "name" => "write a book",
            "description" => "hohoho",
            "created_at" => "2020-07-26T05:47:00.908706Z",
            "updated_at" => "2020-07-26T05:53:00.376501Z"
        ]
    ];
    return response()->json($data);
});

Route::get('/items/{id}', function(Request $request) {
    $data = [
        'id' => 1,
        'name' => "Swim across the River Benue",
        'description' => "ho ho ho",
        'created_at' => "2020-07-26T22:31:04.49683Z",
        'updated_at' => "2020-07-26T22:31:04.49683Z"
    ];
    return response()->json($data);
});

Route::post('/items', function(Request $request) {
    $data = [
        'id' => 1,
        'name' => "Swim across the River Benue",
        'description' => "ho ho ho",
        'created_at' => "2020-07-26T22:31:04.49683Z",
        'updated_at' => "2020-07-26T22:31:04.49683Z"
    ];
    return response()->json($data, 201);
});

Setting up the Vue Frontend

Create a new Vue project in the laravel-cors folder by running vue create frontend. Select the default preset. In the created frontend folder, open the HelloWorld.vue file (at src/components/HelloWorld.vue) and replace its contents with the code block below:

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <p>Open your Browser console to see the headers sent in by Laravel.</p>
  </div>
</template>
<script>
export default {
  name: "HelloWorld",
  props: {
    msg: String,
  },
  mounted() {
    let req = new Request("http://localhost:8000/api/items", {
      method: "get",
    });
    fetch(req).then((response) => {
      for (let pair of response.headers.entries()) {
        console.log(pair[0] + ": " + pair[1]);
      }
    });
  },
};
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h3 {
  margin: 40px 0 0;
}
ul {
  list-style-type: none;
  padding: 0;
}
li {
  display: inline-block;
  margin: 0 10px;
}
a {
  color: #42b983;
}
</style>

The code above makes an HTTP request to the /items route we created back in our Laravel application and logs all of the response headers it receives.

To try things out, serve the Laravel application by running php artisan serve in the server folder from your terminal.

While the server is running, launch the Vue app by changing into the frontend directory and running yarn serve.

Visit the Local URL reported by yarn serve in your browser and open the page console.

You should see the response headers in your console as shown in the screenshot below:

API response headers logged to a Firefox console

Enabling CORS for a Route Prefix

The paths option lets you specify the routes or resource paths for which you want to send CORS headers. In the configuration above, CORS headers are enabled for all routes with the “api” prefix. Removing that prefix or changing it to something else (e.g 'paths' => ['endpoints/*']) disables CORS for our API endpoints and we get an error similar to the one shown below:

Example of a blocked CORS request

Paths could be exact string matches (e.g /public/img/photo.img) or specified using the “*” wildcard (e.g /public/* or /api/*). Additionally, you can enable CORS for files in a given folder and all of its subfolders using the pattern: public/**/*.

Looking up Allowed HTTP Methods

allowed_methods specifies the HTTP request methods allowed when accessing a resource. The methods you add here are returned in the “Allowed-Methods” header when a client makes a preflight request to the Laravel application. By default, Laravel enables CORS for all HTTP methods (by using the “*” value).

For example, replacing the wildcard “*” character in the allowed_methods with “POST” will break our frontend code (since it is making “GET” requests to the server).

Restricting Allowed Hosts

The allowed_origins dictates the “origins” that are allowed to access the resources (origin here refers to the combination of scheme, domain, and port of the URL). Like the options above, it also allows for wildcard matching (e.g *example.com will allow example.com and any of its subdomains to access the resource). It is set to allow all origins by default.

NOTE: When not using a wildcard, the origin must be specified in full (e.g http://example.com is valid, example.com is not).

Restricting Allowed Hosts with Regular Expression

Using the allowed_origins_patterns option, you can specify your allowed origins using Regular Expression. It is useful when you want match patterns more complex than the “*” wildcard supported by allowed_origins. The values provided here must be a valid preg_match() pattern. Also, take care to avoid being accidentally inclusive. For example, /https?:\/\/example\.com/ is valid for both https://example.com and https://example.com.hackersdomain.com. A better pattern is /https?:\/\/example\.com\/?\z/ which limits it to domains ending with example.com (or with an optional slash at the end).

Configuring Allowed Headers

The allowed_headers option defines the HTTP headers that will be allowed in the actual CORS request. It sets the Access-Control-Allow-Headers header sent as a response to preflight requests containing Access-Control-Request-Headers. The generated configuration defaults to allowing all HTTP headers as well.

Exposing Custom Headers

Using exposed_headers, you can allow your API clients to access custom HTTP headers that are not safe-listed by CORS by default. For example, you may want to expose the X-RateLimit-Remaining and X-RateLimit-Limit headers set by Laravel’s throttle middleware to indicate how many more requests a client can make within the given time frame.

There are only seven HTTP headers that are safe-listed by CORS (i.e., Cache-Control, Content-Language, Content-Length, Content-Type, Expires, Last-Modified, Pragma) so you will have to include any other header your application needs to expose in the exposed_headers array.

Caching CORS Responses

The value of the max_age option determines how long (in seconds) a client can cache the response of a preflight request. Laravel sets it to 0 by default. Browsers will ignore the max_age value if it is longer than their maximum limit (24 hours for Firefox, 2 hours for Chromium >= v76).

HTTP Sessions over CORS

supports_credentials allows sending cookies and sessions (which rely on cookies also) over CORS requests. It sets the Access-Control-Allow-Credentials and cannot be true when all origins are allowed (i.e., when allowed_origins is the wildcard (*) character).

Conclusion

Configuring CORS can be a chore sometimes. Laravel now makes it easier with an “out-of-the-box” setup by leveraging the laravel-cors package.

If you are looking to explore CORS generally,

 

Michael Okoko is a software engineer and computer science student at Obafemi Awolowo University, Nigeria. He loves open source and is mostly interested in Linux, Golang, PHP, and fantasy novels! You can reach him via: