Laravel and CORS behind a reverse-proxy

Julien Aupart
Webtips
Published in
5 min readApr 20, 2020

When engaging in a software development project, the tutorial that got our foot on the ladder is rarely enough. We like to combine technologies, and eventually we find ourselves in a case that is a bit special. And in that particular case, it may become hard to find help on the web.

This article is about one of those special cases: the use of Laravel in a Docker multi-container environment, and of a Traefik container as a reverse-proxy. The Laravel application exposes an API, to which our front-end, a VueJS SPA (in another container), connects.

Here is a little schematic of the architecture

Also available in french

Let’s agree on some vocabulary

Before we start, here is some of the terms used in this article.

Container
A container is a software element that can contain a service or an application. To compare to a virtual machine, it does not contain the OS, but rather sits on a container engine (e.g. Docker), which shares the resources of the OS it is installed on with all the containers it manages. The main benefit being the size of container images being far more lighter than the VM images.

CORS
CORS means “Cross-Origin Resource Sharing”. It relates to a mechanism that allows access to resources of an application located on a different name domain than the one the requests originates from.

API
API means “Application Programming Interface”. It is a well defined and documented interface that allows interaction with a software entity. That interface is often published for third-party application to communicate with it. The API is the application’s facade intended for programmers.

So what’s the matter anyway ?

When CORS is enabled in Laravel, and because of our particular situation (containerization and reverse-proxy), the Laravel application wrongly interpret requests coming from it’s own front-end. They are judged as coming from a different origin.

Understanding the how and the why requires some explanations on how various elements work, independently and together.

Traefik and unsecured HTTP links

Traefik is what’s called an Edge Router. It’s a switch that organizes requests traffic between the internet on one side, and our containers on the other. It is the only interface to the outside of this Docker environment. It’s the one taking care of securing data with an SSL certificate (the famous HTTPS thing). In every network inside of our container environment (docker networks), communications between containers is not secure (HTTP). And there is no need for secure communications as those networks are closed, not accessible from the outside of Docker, and we know all of the stakeholders.

Laravel and CORS identification

Since Laravel 7.0, CORS support is handled by the fruitcake/laravel-cors package, automatically added to the composer.json file of the project at it’s creation. If your version of Laravel is older, you can simply add this package to your project.

To figure out if a request comes from an other domain (CORS) or not, Laravel first looks if it has a header called Origin (which we have no power on). If it’s not the case, it’s judged coming from an other origin.

If it’s present on the other hand, which is our case, Laravel looks at it’s value and compares it to what it calls SchemeAndHttpHost. If their values match, then the request is judged coming from the same origin and does not need any other particular processing. If they don’t match however…

It’s the calculation of this SchemeAndHttpHost that can cause a difficulty for us, and especially the Scheme part of it, that can be “http” or “https”. Indeed, Laravel, or rather Symfony (framework upon which Laravel is based) in this case, does a deep check and will return “https” only if the request comes from a trusted proxy (which we would have provided), or if its actually coming from an https connection.

And that is in fact here that lies our problem. As the request received by Laravel comes from Traefik, our reverse-proxy, it comes from an unsecured connection. The value of SchemeAndHttpHost: “http://domain.com", is then different from the value of the Origin header: “https://domain.com". The request is then treated as CORS by Laravel whereas we would have wanted it to be treated as a local request.

The solution

Trusted proxies

Since version 5.5 of Laravel, the fideloper/proxy package appeared in the composer.json file. This package is going to save our live because it allows to easily configure a list of trusted proxies, that is IP addresses for which we want Laravel to consider the connection as secured.

This way, the value of SchemeAndHttpHost computed by Laravel will in fact be “https://domain.com" and our requests coming from our front-end will be treated as intended.

An unpredictable IP

The only trouble is that IP addresses inside of a docker network are not fixed. They can change at every new docker startup, and on top of that, it is impossible to in advance what they will be.

Fortunately, because our Laravel application is inside a container, protected by our reverse-proxy, the only way to access it is through our Traefik container, which handles a secured connection with the rest of the world. We can then affirm with absolute certainty that all of the requests that our Laravel application receives are secured. We can then allow ourselves to consider every IP address as trusted. And this decision won’t affect request actually coming from other domains, because there domain name will actually be different (during the SchemeAndHttpHost comparison).

Let’s actually fix this problem

If the config/trustedproxy.php file is not present in your Laravel project, you can add it using the following command:

php artisan vendor:publish --provider="Fideloper\Proxy\TrustedProxyServiceProvider"

A little trick: With Laravel, you can use the vendor:publish command without any argument. It will then prompt you with an interactive menu to select which content to publish.

Here is what our config/trustedproxy.php file looks like:

To go a little further

To learn a little more on this “trusted proxies” matter, Symfony gives a little manual on How to Configure Symfony to Work behind a Load Balancer or a Reverse Proxy. Le documentation of the fideloper/proxy package on GitLab is also offering a wealth of information.

Configuration

For the sake of reproducibility, here is the version numbers of the different software pieces used for this article :

  • Docker : 19.03.5
  • Docker-compose : 1.25.0
  • Laravel : 7.0
  • Traefik : 2.0
  • fruitcake/laravel-cors : 1.0

I hope you enjoyed this article. You can read the french version of it on the blog of my company, alongside with a lot of other articles on documentation, which is what we do at Naept.

Thanks for reading me. Have a nice day!

--

--