How to Redirect to HTTPS With Play 2.4

To protect your web app or API, there is almost only one way at this time: TLS. But users and browsers don't always use TLS by default. So what you want is to redirect them to a TLS encrypted version of your site if they try to connect via plain http.

Here is how to do it with Play Framework 2.4 in scala:

Play! and HTTP filters

We will start by creating a "TLSFilter.scala" file and write a TLSFilter class in it:

class TLSFilter extends Filter {
  def apply(nextFilter: RequestHeader => Future[Result])
    (requestHeader: RequestHeader): Future[Result] = {
      if(!requestHeader.secure)
        Future.successful(Results.MovedPermanently("https://" + requestHeader.host + requestHeader.uri))
      else
        nextFilter(requestHeader).map(_.withHeaders("Strict-Transport-Security" -> "max-age=31536000; includeSubDomains"))
  }
}

This part is easy: we implement the apply function by just checking the secure value of RequestHeader. If the connection is not secure, we need to redirect the client to the same url only with "https" as the protocol. If the connection is secure, we pass the request to the next header. Nothing simpler.

Note that we use requestHeader.host instead of requestHeader.domain because the host value is actually the value of the Host header as set by the client, with optional port and stuff.

Note that we create a Filter implementation and not an EssentialFilter one because we do not care about the body.

Next, you need to create a HttpFilters implementation that will hold the instance of your TLSFilter:

// In TLSFilter.scala
class MyFilters extends HttpFilters {
  val filters = Seq(new TLSFilter)
}

And finally, you need to tell Play! to use your Filters class:

# In conf/application.conf
play.http.filters=my.package.MyFilters

Now it will check your requests and permamently redirect the clients to HTTPS.

But, how does Play! know that the request is secure?

Reverse proxies: where did the 's' go?

Now, we need to ensure that Play! knows to differentiate a secured connection from a plain one. If you configured HTTPS in your application, it's quite simple to understand. But it is not always the case:

You most probably did not, configure TLS in your application. And that is because you deployed it on a very powerful and developer-friendly PaaS. So, chances are your Play! application is getting requests in plain HTTP, because the TLS encryption ended at the front reverse-proxy that's piping the request towards your app.

How then is your application going to know that the connection is secured? Enter the non-standard and the standard ways.

X-Forwarded-Proto

The first, non-standard but widely used (e.g. at Clever Cloud) way to know if the request handled by the reverse proxy in front came in a secure channel is to check the X-Forwarded-Proto HTTP header. Like all non-standard headers, you can recognize it by the X- at the beginning of the name.

This header describes how the final client is communicating with the reverse-proxy.

It takes two values: http and https. You can check for that header in your application. But we will see below that Play! can do it by itself.

RFC 2739

Also called the Forwarded HTTP Extension, it standardize the way that a proxy tells the final endpoint what is going on between the final client and itself. It's been published in June 2014.

You can read it here: https://tools.ietf.org/html/rfc7239. But the only thing that is relevant for us is the proto parameter. Like X-Forwarded-Proto earlier, its values that interest us are http and https. Like for the other one, Play! can handle those values for you, if you ask nicely.

How to make Play! handle Forwarded headers?

At the time of this writing, Play! framework support for Forwarded headers have known many states:

  • In Play! 1.x, you need to add XForwardedSupport=all in your application.conf
  • In Play! 2.0 to 2.3, you need to add trustxforwarded=true in your application.conf

Both these ways only support the X-Forwarded-Proto header.

Now, in Play! 2.4.x, the philosophy is different:

  • Define the version of the Forwarded header you want to use: play.http.forwarded.version=x-forwarded|rfc7239
  • Set the proxies you trust: play.http.forwarded.trustedProxies=["proxy_ip1","proxy_ip2",…].

Of course proxy_ipX can be an actual IP or a subnet mask, like "0.0.0.0" or "::" to trust every IPv4 or v6, respectively. Defaults are "127.0.0.1" and "::FF".

Also, as the X-Forwarded-Proto header is the one that is widely used in the world, the version default value is "x-forwarded".

What the hell is Strict-Transport-Security?

As you read the filter code, you must have seen that in the case the request is already in HTTPS, we still add a header to the response: Strict-Transport-Security: max-age=31536000.

This is the HTTP Strict Transport Security (HSTS) header. What it does is basically telling the client (most likely a browser): "Next time (and for the next 31536000 seconds), if your user tries to load the unencrypted version of the site, don't wait for me to redirect you and use https already".

The browser (meaning: chrome >= 4.0.211.0, firefox >= 4.0, Opera >= 12, IE >= 11) will then save the website and automatically replace "http" by "https" in the requests the next times.

This mechanism is documented here: https://www.rfc-editor.org/rfc/rfc6797.txt.

You MUST set the max-age value. You can also add includeSubDomains (after a ";" of course), which means "if you get that header while requesting domain.com, please use HTTPS when requesting *.domain.com too". It is a good practice to always add includeSubDomains just in case.

Please note that the STS header can only be set if the website is already TLS protected. You MUST NOT set this header on a non-TLS response.

If you want the browsers to use HSTS before the first request, you can register your domain to be included in browsers preload lists. To achieve that, register your domain here: https://hstspreload.appspot.com/. Also add the preload value to the header, like that: Strict-Transport-Security: max-age=31536000; preload.

Blog

À lire également

SuperBOL: The COBOL revolution in the Cloud

COBOL, a programming language that is over 60 years old, continues to power a large proportion of the IT systems of the world's major companies, particularly in the financial and insurance sectors.
Features

Clever Cloud welcomes the first startups to the UP Programme

Clever Cloud is proud to announce the arrival of the first five startups selected to join its UP Programme, an initiative dedicated to supporting young technology companies in their growth phase.
Company

A minor update resulted in a cascade of errors: how it went wrong, what we’ve learnt

On Friday, August 2nd, 2024 Clever Cloud’s platform became very unstable, leading to downtime of varying duration and scope, for customers using services on the EU-FR-1 (PAR) region, and remote zones depending on the EU-FR-1 control plane (OVHcloud, Scaleway, and Oracle). Privates and on-premise zones weren’t impacted.
Company Engineering