fredag 30. mars 2012

Using Unfiltered and HTTPS on Heroku

A few weeks ago I started a pet-project using Scala and Unfiltered. Basically I wrote a new front-end for an Internet-explorer-only website. Now, the website I'm scraping requires the user to log in, which in turn means that I have to handle a user's username and password. Obviously I don't want those to be transmitted in plain text over http, that would just be irresponsible. Luckily, Heroku offers HTTPS support out of the box, with the piggyback option that allows you to use the Heroku SSL certificates. This means that all resources available on http://yourapp.herokuapp.com/ will also be available with en encrypted connection on https://yourapp.herokuapp.com. But how does it work?

Let Heroku do HTTPS, you do HTTP
The Heroku router will be the entry point that does the actual HTTPS communication with the client, then the router will forward the calls to your server (Jetty in my app) over HTTP. This means that the data is unencrypted between the Heroku router and your app, but if you are not a tin-foil-hat kind of guy, you'll be OK with that.

But those pesky users are accessing my app using HTTP
Here comes the problem, the app is available both through HTTP and HTTPS, and while you aren't into tinfoil hats, you don't want your non-IT-savy users' credentials freely available for anyone with Firesheep on that insecure internet cafe WIFI. What you want to do is to enforce HTTPS. But how do you do that? Your app is configured to run HTTP, not HTTPS.

Developer, meet x-forwarded-proto
Well, Heroku sets some header values that indicate wether your app is being accessed using HTTP or HTTPS, say hello to x-forwarded-proto. This header is set by the Heroku router, and will be either HTTP or HTTPS. So, by querying that value, you'll be able to see how your app was accessed. We now have a way to figure out how the app is accessed, all that is left to do now is to enforce HTTPS.


The following extractor will help you figure out what protocol was used to access your app
object XForwardProto extends StringHeader("x-forwarded-proto")

You can now use it in your Plans like this:
case r@POST(Path(Seg("login" :: Nil))) & XForwardProto("https") => //Do login
to only respond to logins over HTTPS for example.

Force HTTPS
What would be even better than only responding to https would be to redirect all http traffic to https. To do that I wrote (or to be fair, it was mostly my good colleague Erlend's work) this HerokuRedirect object:

object HerokuRedirect {
  def apply[A, B](req: HttpRequest[A], path: String): ResponseFunction[B] = {
    val absolutepath = if (path.startsWith("/")) path else "/" + path
    req match {
      case XForwardProto(_) & Host(host) => Found ~> Location("https://%s%s".format(host, absolutepath))
      case _ => Redirect(absolutepath)
    }
  }
Now, you can add to your Plan (preferably the first case of your first Plan in Unfiltered) a redirect for all incoming http requests, like this:
case r@GET(_) & XForwardProto("http") => HerokuRedirect(r,r.uri)
Quite nice, hu?

More uses for the XForwardProto
I'm also using the XForwardProto when setting cookies, specifically when setting the secure flag, like this:
        val secure = req match { case XForwardProto("https") => Some(true) case _ => Some(false)}
        SetCookies(Cookie(name = "someKey", value="some value", secure = secure)) ~>
          Html5(

Some text

)
Of course I could have set the secure flag always, but then testing on localhost would be a pain, this way I can test without https on localhost, and still enforce https only on heroku.

Ingen kommentarer:

Legg inn en kommentar