Configuring CORS for Laravel Public Storage

Written By Rachel Opperman
Posted on

Recently, I was tasked with implementing a feature that would pull a few primary colors from a photograph. After a bit of sleuthing, I found a Node package that looked like it would work well, so I pulled it into my Nuxt project and started tinkering. Unfortunately, it didn’t take too long for a crippling issue to show up. Follow along to learn what the problem was and how I ended up solving it. (Don’t have time for the full explanation? Skip to the TL;DR below for a summary.)

There are certain JavaScript packages, such as node-vibrant, that take public resources (e.g., images) and draw them to a canvas using the Canvas API. If those resources don’t have an 'Access-Control-Allow-Origin' header, then a CORS error will be thrown when the image is drawn into the canvas. With a Laravel API, publicly accessible image resources (e.g., user avatars) are typically stored on the public storage disk (i.e., in the public directory). The problem is this: Resources stored in the public directory do not have an 'Access-Control-Allow-Origin' header by default.

This post will discuss why the inner workings of the node-vibrant package dictate that CORS headers are present on the images being passed into it, why errors will be thrown if those headers are not present, and how to configure an Nginx server to add an 'Access-Control-Allow-Origin' header to the resources in a Laravel API’s public storage.

TL;DR

Resources in Laravel’s public storage do not have CORS headers by default, which can cause CORS errors when trying to place those resources into a <canvas> element in the browser. Using a CORS package (such as this one) to add CORS headers will not work in this instance because middleware is not applied to the public directory. So, CORS headers must be added manually by configuring the server. In order to add an 'Access-Control-Allow-Origin' header to every resource in public storage (the storage directory), the following server configuration can be added.

Note: This example is specific to Nginx.

location /storage/ {
    add_header 'Access-Control-Allow-Origin' '*';
}

What’s important to note here is the '*'. This indicates that any origin (i.e., any domain) can access the resources in public storage. Therefore, it is essential to be certain that those resources can safely be accessed by anyone. Otherwise, it’s best to indicate the single origin that should be permitted to access those resources (e.g., http://localhost:3000 for development and https://myuser.guide for production).

If you’re unfamiliar with Laravel, you may be wondering why it’s /storage/ and not /public/. It’s because the public disk stores the files in the public directory in storage/app/public by default.

Read on if you’re interested in more about CORS and the details of why node-vibrant requires this CORS header.

CORS Background

CORS is an acronym for Cross-Origin Resource Sharing, and it’s a mechanism that, simply put, utilizes additional HTTP headers to instruct a web browser that’s running a web app on one origin to allow access to selected resources from a different origin.

An in-depth explanation of CORS is beyond the scope of this post, but more information can be found here.

Why node-vibrant Requires CORS Headers

If an image without a CORS header is passed into node-vibrant, an error similar to the following will occur:

Screen Shot 2020 02 20 At 12 32 26 Pm

Let’s delve into why this happens.

The browser requested an image resource from the Laravel API. The image was stored in Laravel’s public storage (the storage directory). The browser’s origin was http://localhost:3000, while the API’s origin was http://api.myuserguide.test.

For security reasons, cross-origin HTTP requests (such as the one being made from the browser to the API) are restricted and follow the same-origin policy. This essentially means that browsers can only request resources from the same origin, unless the response from the other origin includes an 'Access-Control-Allow-Origin' header that specifies the browser’s origin.

Packages like node-vibrant take an image path (which retrieves the image) and use the Canvas API to perform some action on the image (in this case, to generate a color palette from the image). When data that was loaded from a different origin is drawn into a canvas, the canvas becomes tainted, meaning that it is no longer considered secure and any attempts to retrieve the image from the canvas will result in an exception being thrown. If the source of the foreign content is an <img> element, retrieving the contents of the canvas is not permitted. Additionally, node-vibrant calls getImageData() on the canvas’ context, which, when called on a tainted canvas, results in an error.

As mentioned above, there are packages available that allow for adding CORS headers to responses sent from a Laravel API. They do so by applying middleware to all of the API’s routes. However, such middleware is not applied to the public directory, and that is often the storage location for resources such as user avatar images. Therefore, when those resources are returned to the browser, they do not have the necessary CORS headers applied.

Therein lies the problem. The image that was being drawn into a <canvas> element did not have the CORS headers required by the Canvas API, so errors were thrown as a result. That’s why the error shown above stated that no 'Access-Control-Allow-Origin' header was present on the requested resource (i.e., the image).

How to Fix It

As mentioned above, this problem can be solved by ensuring that resources in the API’s public directory have that required CORS header, which is done by updating the server configuration.

If you’re using Homestead to locally host your API, you can find your server configuration by entering the following command:

# Example site name: api.myuserguide.test
$ sudo nano /etc/nginx/sites-available/<site_name>

Once you have added the required code to the configuration file, you can save the file and exit Nano. All that’s left to do is reload the server, which can be done with the following command:

$ sudo service nginx reload

Instructions for configuring production servers (or non-Nginx servers) will, of course, vary.

It’s worth noting that these headers are not required in all instances. For example, if the image is just being displayed in the HTML via a normal <img> element, and is not being drawn into a <canvas> element, then a CORS header is not necessary.


So there you have it! Let’s do a quick recap:

  • The node-vibrant package uses the Canvas API to draw an image into a canvas. For security reasons, that image must have an 'Access-Control-Allow-Origin' header.
  • If the image is being retrieved from a Laravel API’s public storage, it will not have that header by default, so the server must be configured to add it.
  • The configuration is as follows:
location /storage/ {
    add_header 'Access-Control-Allow-Origin' '*';
}
  • Please remember -- only use the '*' if you’re positive that any domain can access the resources. Otherwise, provide a specific origin in its place.

If you have any thoughts, or alternate methods for solving a similar problem, please reach out to us @zaengle and let us know.

  1. Hero image