Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to force Play framework 2 to always use SSL?

I have a Play Framework app running on Heroku, using Heroku's SSL endpoint.

I would like to make all pages available via SSL only.

What's the best way to that?

So far, my best solution is to use onRouteRequest in my GlobalSettings and route non-SSL requests to a special redirect handler:

override def onRouteRequest(request: RequestHeader): Option[Handler] = {
  if (Play.isProd && !request.headers.get("x-forwarded-proto").getOrElse("").contains("https")) {
    Some(controllers.Secure.redirect)
  } else {
    super.onRouteRequest(request)
  }
}

and

package controllers

import play.api.mvc._

object Secure extends Controller {

  def redirect = Action { implicit request =>
    MovedPermanently("https://" + request.host + request.uri)
  }
}

Is there a way to do this entirely from within GlobalSettings? Or something even better?

like image 252
Mike Roberts Avatar asked Oct 02 '13 21:10

Mike Roberts


4 Answers

Here is another way using filter. This also uses strict transport security to make sure that future requests go to https.

object HTTPSRedirectFilter extends Filter with Logging {

    def apply(nextFilter: (RequestHeader) => Future[SimpleResult])(requestHeader: RequestHeader): Future[SimpleResult] = {
        //play uses lower case headers.
        requestHeader.headers.get("x-forwarded-proto") match {
            case Some(header) => {
                if ("https" == header) {
                    nextFilter(requestHeader).map { result =>
                        result.withHeaders(("Strict-Transport-Security", "max-age=31536000"))
                    }
                } else {
                    Future.successful(Results.Redirect("https://" + requestHeader.host + requestHeader.uri, 301))
                }
            }
            case None => nextFilter(requestHeader)
        }
    }
}
like image 88
Dimitry Avatar answered Nov 04 '22 14:11

Dimitry


We have done that much like you but with a play filter that generates a MovedPermanently instead of a controller method.

I don't think there is a better way with heroku, or at least we couldn't find any feature to disable unencrypted HTTP.

like image 43
johanandren Avatar answered Nov 04 '22 13:11

johanandren


Here is the solution for the Java version of Play Framework.

Add the following to the Global.java file:

@Override
public Handler onRouteRequest(RequestHeader request) {
    String[] x = request.headers().get("X-Forwarded-Proto");
    if (Play.isProd() && (x == null || x.length == 0 || x[0] == null || !x[0].contains("https")))
        return controllers.Default.redirect("https://" + request.host() + request.uri());
    return super.onRouteRequest(request);
}
like image 2
plade Avatar answered Nov 04 '22 14:11

plade


In Play 2.5.x Java (I think should work in play 2.4.x as well but using Promise.F instead of CompletionStage), I added a filter:

public class TLSFilter extends Filter {
    @Inject
    public TLSFilter(Materializer mat) {
        super(mat);
    }

    @Override
    public CompletionStage<Result> apply(Function<Http.RequestHeader, CompletionStage<Result>> next, Http.RequestHeader rh) {
        if (Play.current().isProd()) {
            String[] httpsHeader = rh.headers().getOrDefault(Http.HeaderNames.X_FORWARDED_PROTO, new String[]{"http"});
            if (Strings.isNullOrEmpty(httpsHeader[0]) || httpsHeader[0].equalsIgnoreCase("http")) {
                return CompletableFuture.completedFuture(Results.movedPermanently("https://".concat(rh.host().concat(rh.uri()))));
            }
        }
        return next.apply(rh).toCompletableFuture();
    }
}

And then add it to the list of filters to use it:

public class AppFilters extends DefaultHttpFilters {

    @Inject
    public AppFilters(TLSFilter tlsFilter, GzipFilter gzipFilter) {
        super(tlsFilter, gzipFilter);
    }
}

And then to use your filters add the following inside application.conf :

play.http.filters = "filters.AppFilters"

And please note, if you have a request handler enabled (look for play.http.requestHandler inside the application.conf file), filters will not work, I suggest to handle requests using filters and remove your current requestHandler.

like image 2
Al-Mothafar Avatar answered Nov 04 '22 14:11

Al-Mothafar