Laravel 5.5 Now Includes TrustedProxy

Laravel v5.5 was released just a week ago at Laracon EU. You may have noticed that the v5.5 composer.json file requires the fideloper/proxy composer package. For me, this is one of those packages I must include immediately on every project because I use Amazon Web Services and Google Cloud every day, so I am thankful Laravel 5.5 includes this package by default.

Setting up the package is a breeze, and I especially appreciate that this package takes care of redundant setup. Let’s briefly cover what this package provides for Laravel, and why it’s an important package in the Laravel ecosystem.

What Does the TrustedProxy Package Do?

On a high level, Trusted Proxy tells Laravel about proxies that can be trusted and how to map X-Forwarded-* headers from the request.

The package readme probably does a better job summarizing:

Setting a trusted proxy allows for correct URL generation, redirecting, session handling and logging in Laravel when behind a proxy.

This is useful if your web servers sit behind a load balancer, HTTP cache, or other intermediary (reverse) proxy.

…

Laravel uses Symfony for handling Requests and Responses. These classes have the means to handle proxies. However, for security reasons, they must be informed of which proxies to “trust” before they will attempt to read the X-Forwarded-* headers.

Laravel does not have a simple configuration option for “trusting” proxies out of the box. This package simply provides one.

Working with Proxies

It’s commonplace for developers to need to work with cloud providers like Amazon Web Services and Content Delivery Networks (CDN) like Cloudflare for full site delivery, with the application sitting behind these services instead of being exposed directly to the world. Also, your application might even be behind a chain of proxies.

When your website or application has DNS pointed at CloudFlare, for example, the HTTP requests get proxied from CloudFlare to your application.

For example, ou might notice a few CloudFlare headers in the HTTP responses on Laravel News:

You can see a couple headers (CF-RAY and Server for example) being sent back in the cURL response. CloudFlare is proxying the request to the actual application, getting the response, appending a few headers, and sending the response back to the end-user.

Since CloudFlare is proxying between the end user and the application, all requests look the same to the application. To let the application know important details about the originating request, proxies send along X-Forwarded* headers.

X-Forwarded-Host – a de-facto standard header for identifying the original host requested by the client in the Host HTTP header (reference)

X-Forwarded-Proto – a de-facto standard header for identifying the protocol, such as HTTP or HTTPS (reference)

X-Forwarded-Port – helps you identify the port that the client used to connect to the load balancer (reference)

Not all proxies use the de-facto standard headers, but this package can help you map those headers so that the underlying Symfony request object knows how to trust the proxy and get the correct values.

HTTPS -> HTTP

If you terminate TLS/SSL at the load-balancer level, your application might receive requests internally on HTTP, but actually the originating request from users is HTTPS. If you are in this situation, your application will receive header like this (among others) from the proxy:

X-Forwarded-Proto: https

The TrustProxies middleware automatically makes the Request object aware of the proxy headers by calling Symfony’s HttpFoundation Request::setTrustedProxies() method, and thus any PHP-generated URIs will know to use HTTPS, even though the request was made via HTTP. Without calling setTrustedProxies(), a Laravel application wouldn’t know about the originating request and how to properly deal with that request.

Configuration

The fideloper/proxy package provides the following Laravel configuration so you can adapt the package to work in a variety of settings, including providing mapping if your proxies use non-standard header names:

<?php
return [
/*
* Set trusted proxy IP addresses.
*
* Both IPv4 and IPv6 addresses are
* supported, along with CIDR notation.
*
* The "*" character is syntactic sugar
* within TrustedProxy to trust any proxy
* that connects directly to your server,
* a requirement when you cannot know the address
* of your proxy (e.g. if using Rackspace balancers).
*
* The "**" character is syntactic sugar within
* TrustedProxy to trust not just any proxy that
* connects directly to your server, but also
* proxies that connect to those proxies, and all
* the way back until you reach the original source
* IP. It will mean that $request->getClientIp()
* always gets the originating client IP, no matter
* how many proxies that client's request has
* subsequently passed through.
*/
'proxies' => [
'192.168.1.10',
],
/*
* Or, to trust all proxies that connect
* directly to your server, uncomment this:
*/
# 'proxies' => '*',
/*
* Or, to trust ALL proxies, including those that
* are in a chain of forwarding, uncomment this:
*/
# 'proxies' => '**',
/*
* Default Header Names
*
* Change these if the proxy does
* not send the default header names.
*
* Note that headers such as X-Forwarded-For
* are transformed to HTTP_X_FORWARDED_FOR format.
*
* The following are Symfony defaults, found in
* \Symfony\Component\HttpFoundation\Request::$trustedHeaders
*
* You may optionally set headers to 'null' here if you'd like
* for them to be considered untrusted instead. Ex:
*
* Illuminate\Http\Request::HEADER_CLIENT_HOST => null,
*
* WARNING: If you're using AWS Elastic Load Balancing or Heroku,
* the FORWARDED and X_FORWARDED_HOST headers should be set to null
* as they are currently unsupported there.
*/
'headers' => [
(defined('Illuminate\Http\Request::HEADER_FORWARDED') ? Illuminate\Http\Request::HEADER_FORWARDED : 'forwarded') => 'FORWARDED',
Illuminate\Http\Request::HEADER_CLIENT_IP => 'X_FORWARDED_FOR',
Illuminate\Http\Request::HEADER_CLIENT_HOST => 'X_FORWARDED_HOST',
Illuminate\Http\Request::HEADER_CLIENT_PROTO => 'X_FORWARDED_PROTO',
Illuminate\Http\Request::HEADER_CLIENT_PORT => 'X_FORWARDED_PORT',
]
];

The configuration allows you to define the IP addresses you want to trust, or you can trust all direct proxies with * and any proxy in a chain of proxies with **. Please consult the documentation carefully, as well as the final part of this article to read about locking down your applications that are behind proxies.

You can create the config/trustedproxy.php configuration by running vendor:publish:

Some reverse proxies (like Amazon’s Elastic Load Balancers) don’t have a static IP address or even a range that you can target with the CIDR notation. In this case, you’ll need to – very carefully – trust all proxies.

Configure your web server(s) to not respond to traffic from any clients other than your load balancers. For AWS, this can be done with security groups.

Once you’ve guaranteed that traffic will only come from your trusted reverse proxies, configure Symfony to always trust incoming request.

Check out fideloper/proxy, which has an extensive readme on how to set up the TrustProxies middleware and configuration with a ton of information on the subject.