Hamburger Icon

Laravel CORS Guide:
What It Is and How to Enable It

stackhawk

StackHawk|April 21, 2021

Learn what CORS is, how to configure it in Laravel, and the relevant configuration options to expose your application for cross-origin requests securely.

Have you ever put JavaScript code on a website that was supposed to fetch data from a remote server, only to realize that it didn’t work? Then you probably looked at your browser’s developer tools and noticed an error message referring to CORS or the same-origin policy. This article is for you if the remote server is under your control and its server-side code is a Laravel application. To fix your issues, we’ll walk through the process of setting up CORS in Laravel through a sequence of questions. 

By going through the process step by step, you’ll learn what CORS is, whether you need it at all, whether you should configure it in Laravel, how to do that, and which configuration options are relevant. By the end, you’ll have learned how to expose your application for cross-origin requests securely. 

Let’s get started! 

What Is CORS?

CORS is a security feature to prevent unauthorized access. It stands for Cross-Origin Resource Sharing

In a browser context, the term “origin” refers to the protocol and hostname parts of a URL. For example, if your full URL is https://example.com/directory/page.html, the origin would be https://example.com. Therefore, the origin groups a set of URLs under the control of the same individual or organization that can safely share things like cookies under the same-origin policy. The same policy restricts websites from making HTTP requests to third-party resources. 

CORS is a mechanism based on HTTP headers that specify exceptions to the same-origin policy and allow cross-origin requests under specific circumstances. A cross-origin request is a website at one origin, such as https://example.com, accessing a resource on a different origin, such as https://example.net. 

Do You Need CORS?

Because it’s a security feature, your default strategy should be to enable CORS only when you’re sure that you need it, and only where you need it. 

First of all, not every cross-origin request requires CORS. Embedding an image, media file, IFrame, CSS stylesheet, or JavaScript library from another domain isn’t subject to the same-origin policy. Only direct requests from scripts, such as API calls through the fetch() or XMLHttpRequest interfaces (and their abstractions), web fonts, and some canvas and WebGL features use CORS. You need to enable it in your Laravel project when you’re at the receiving end of relevant cross-origin requests. 

We’ll look at two scenarios where you need CORS: 

  • Your Laravel website exposes a clearly defined (RESTful) API for others. You also expect your application programming interface consumers to make API calls directly on their websites through the user’s browser (instead of their servers).

  • Your Laravel project exposes APIs or other endpoints only for internal use, but your website spans multiple domains or subdomains. A particular case of this is when you want to access the production back end while developing the front end on localhost or a staging server.

In both cases, you should expose the minimum number of endpoints to foreign origins. If you don’t want others to use your APIs, make sure that they can’t. Imagine you have two HTTP endpoints—one for internal and one for external use. According to Hyrum’s Law, even if you only document the external endpoint, someone will eventually find about the internal endpoint and start using it. 

If your website has neither of these use cases, then it’s likely you don’t need CORS. 

Where Should You Configure CORS?

Configuring CORS in your Laravel project is just one option. Here are some others: 

  • You could also configure CORS somewhere in the web server or on a reverse proxy, CDN, or whatever is in between your Laravel code and the user.

  • If you’re on Apache, you can create a .htaccess file.

  • If you use Cloudflare, you can deploy a Worker that handles CORS.

Sometimes these options are better—for example, when you host a static web font on your server and need to add the CORS headers to it. That file might go straight through your web server and not through Laravel and PHP anyway. 

In every other case, however, I’d always recommend doing the CORS configuration in Laravel. The advantage is that you keep it close to the development and ensure you can configure the minimal surface area according to your application’s needs. 

The most unfortunate thing would be if multiple layers of your stack tried to handle CORS independently. Such a setup could lead to duplicate headers and eventually unexpected and undocumented behavior. 

Once you’ve decided to let Laravel take your cross-origin operations, make sure to communicate that with your system admin or DevOps person, and not let anyone try changing it at the web server or CDN layer. 

Which Version of Laravel Do You Use?

The standard way to add CORS support in Laravel used to be a third-party package from Dutch developer Barry vd. Heuvel. But starting with version 7, CORS became a first-class citizen in Laravel. The same library became part of the main distribution, so it works the same way. 

Hence, if you are on Laravel 7 or a newer version, CORS support is already enabled. In some cases, you don’t even need to change the default configuration. I’d still recommend going through all the options and making appropriate adjustments by editing the configuration file. If you like, you can skip forward to the next section, where I’ll explain how to find and edit this file. 

What if you’re on Laravel 6 or older and upgrading the framework is not an option? In that case, you need to install and configure the library separately before the configuration file becomes available. To do so, open a terminal or command prompt, navigate to your project directory, and run the following command: 

composer require fruitcake/laravel-cors

Then, make sure that the CORS class is part of your global middleware stack. Again, adding the middleware is only necessary for the older versions of Laravel in which you installed the library yourself. 

Open the file app/Http/Kernel.php in your IDE or code editor. The file will have a different configuration for each project, of course, but it will generally look like this: 

PHP
<?php

namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel {

  /**
   * The application's global HTTP middleware stack.
   *
   * @var array
   */
  protected $middleware = [
    \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
    \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class
  ];

  /**
   * The application's route middleware.
   *
   * @var array
   */
  protected $routeMiddleware = [
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
  ];

}

What you need to pay attention to is the $middleware array. Add the following line to the array, ideally on top as the first middleware in the stack: 

\Fruitcake\Cors\HandleCors::class

The file should look like this afterward: 

PHP
<?php

namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel {

  /**
   * The application's global HTTP middleware stack.
   *
   * @var array
   */
  protected $middleware = [
    \Fruitcake\Cors\HandleCors::class,
    \Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
    \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
    \Illuminate\Session\Middleware\StartSession::class,
    \Illuminate\View\Middleware\ShareErrorsFromSession::class
  ];

  /**
   * The application's route middleware.
   *
   * @var array
   */
  protected $routeMiddleware = [
    'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
  ];

}

A middleware intercepts every request. During execution, it inspects its request headers, handles preflight requests itself, and adds the necessary response headers that the browser expects. In other words, it handles everything you need to grant third-party access. The advantage of the middleware is that you can configure CORS at a single place in your code. Hence, you don’t have to worry about it in every route. 

Which of Your Routes Need Third-Party Origin Access?

Go to the config directory in your Laravel project and open the file cors.php. It contains all the necessary configuration options, which we’ll discuss throughout this article. The following example shows the file as it looks in a fresh Laravel 8 install: 

PHP
<?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/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['*'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => false,

];

The most important option is paths, which defines all the routes that need cross-origin access. As of Laravel 8, the default paths entries are [‘api/*’, ‘sanctum/csrf-cookie’]. With this, all API routes, but none of the web routes, are accessible with CORS. There’s also support for Laravel Sanctum, which handles CSRF for SPAs (single-page applications). You can remove it if you don’t need it. 

By the way, just like CORS, CSRF is a cross-origin security issue that you should always keep in mind. You can learn more about these issues in StackHawk’s article on Dynamic Application Security Testing (DAST)

If you want to be on the safe side, you can be even stricter and replace api/* with all the API routes for which you expect cross-origin traffic. That could be a smaller subset of your API. 

Closely related to this is the allowed_methods option, which defines the allowed HTTP verbs (such as GET and POST) and defaults to [‘*’], so all verbs are permitted. I recommend keeping it that way, as the HTTP method choice isn’t the best way to define your exposed cross-origin API’s surface area anyway. 

Which Origins Need Access to Your API?

Using CORS, you can define the origin hostnames that you permit to access your endpoints. Generally, there are two approaches. One is to allow just about any origin by using the asterisk (*) as a wildcard. The other is to list the hostnames explicitly. 

The choice brings us back to the two scenarios we mentioned above: 

  • Suppose you use your API endpoints internally on a website spread over multiple domains and subdomains or from a development environment. In that case, you can list them all in the allowed_origins configuration option explicitly. In that way, you make sure nobody else can access them. CORS doesn’t support wildcard subdomains (such as *.example.org) natively, but the Laravel CORS middleware does. Therefore, you can use wildcards to enable all subdomains on your main domain, for example. If you need more advanced templating, you can use allowed_origins_patterns and write a regular expression

  • If you have a public API endpoint, you can use the general wildcard (*) in the allowed_origins configuration option. Thus, you allow anyone to make CORS requests for that endpoint.

Here’s an example of a custom CORS configuration file: 

PHP
<?php
  return [
'paths' => ['api/getList', 'api/getDetails/*'],
'allowed_methods' => ['GET'],
'allowed_origins' => ['example.com', '*.example.org'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,

];

In the example above, the developer decided to grant access to two specific API endpoints to two different origins. One of each is a wildcard. 

Pro tip: As an API provider, you may want to hand out API keys to your consumers. You could tie every API key to the specific origin that the consumer provides. Sadly, this is not in the standard CORS middleware’s scope, so you’d have to set your allowed_origins to * and check the Origin header in another place. That place could be your controller code or a custom middleware. Due to the complexity of this option, we can’t go deeper into it in this article. 

Which Other Configuration Options Are Relevant?

There are four more configuration options: 

  • allowed_headers

  • exposed_headers

  • max_age

  • supports_credentials

The allowed_headers and exposed_headers options control the HTTP request and response headers that your Laravel application and a browser on a different origin can exchange. The defaults are [‘*’] for allowed_headers and false for exposed_headers. As a result, clients can send any request headers, but the server can only use standard response headers. 

These defaults are probably sensible. Check your API implementation, though. If you ever send custom headers, you’ll need to expose them, too. 

Some CORS requests require preflights, which are HTTP OPTIONS requests that precede the actual API call. To minimize those with caching, change the max_age default 0 to a higher value. That comes in handy when you expect many round trips between client and server. Most browsers cap this value, though, so something short like 600 seconds (10 minutes) makes sense. 

Finally, the supports_credentials option indicates whether you can use cookies or other credentials with your requests. Once again, think about your scenario. 

  • An open API endpoint doesn’t require cookies because it’s either public or uses API keys for control. For those, you can keep this as false.

  • For an SPA where your users log in, though, you probably need credentials, so make sure to set it to true.

Are There Other Caveats?

Laravel middleware relies on processing your HTTP requests through the framework. If you use PHP commands like echodie, or exit in your routes, then you’re shortcutting the framework. That may result in the wrong HTTP headers. If you rely on cross-origin requests during debugging, avoid any PHP methods that generate output and stop the framework’s workflow. 

Watch a demo to see StackHawk in flight

To Summarize

When you expose public API endpoints or use AJAX-style requests in an SPA spread over multiple hostnames with a Laravel back end, you need to configure CORS. Older Laravel versions need a plugin, whereas newer versions support this out of the box. 

Make sure to review all configuration options. That way, all necessary requests will work with CORS and you’ll minimize the exposed surface area at the same time. 

To learn more about enhancing your Laravel application’s security configuration, check out this SQL injection guide

This post was written by Lukas Rosenstock. Lukas is an independent PHP and web developer turned API consultant and technical writer. He works with clients of all sizes to design, implement, and document great APIs. His focus is on creating integrations and mashups that highlight the values of APIs, and educate developers about their potential.


StackHawk  |  April 21, 2021

Read More

Add AppSec to Your CircleCI Pipeline With the StackHawk Orb

Add AppSec to Your CircleCI Pipeline With the StackHawk Orb

Application Security is Broken. Here is How We Intend to Fix It.

Application Security is Broken. Here is How We Intend to Fix It.

Using StackHawk in GitLab Know Before You Go (Live)

Using StackHawk in GitLab Know Before You Go (Live)