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

Clever Cloud structures itself to support its organic growth

In 2023, Clever Cloud has once again made great strides, with a significant increase in its turnover. Having recently passed the 60-strong mark, the company is welcoming new profiles to support its development, and is expanding its Management Committee.
Company Press

Clever Cloud and CISPE: a strategic commitment to the European Cloud

Continuing its commitment to digital sovereignty, Clever Cloud is proud to announce its participation in CISPE (Cloud Infrastructure Services Providers in Europe association).
Company

Clever Cloud opens a new Gravelines HDS region

Clever Cloud opens a new Gravelines Health data Hosting region to have redundancy on those sensitive datas.
Company