21

I want to enforce https routing for the login page only of my application.

Is it possible to do so with Play! without the use of a front end http server?

emt14
  • 4,846
  • 7
  • 37
  • 58
  • 2
    Why always that someone asks a Play framework related question there are answers for 1.x and 2.x versions? Given they are so different this just cause confusion, if there were just named with different names... Like `Play` and `Run` maybe. – Jaime Hablutzel Sep 07 '14 at 08:59
  • Everyone told the Play! team this when they launched 2.0. They didn't listen. – HelpMeStackOverflowMyOnlyHope Feb 17 '16 at 11:07

6 Answers6

21

You can use an @Before interceptor to redirect every request, even if the user types http:// directly. Below is the code that I use (it works when running containerless play run, or when running behind a front end such as on Heroku).

public class HttpsRequired extends Controller {
    /** Called before every request to ensure that HTTPS is used. */
    @Before
    public static void redirectToHttps() {
        //if it's not secure, but Heroku has already done the SSL processing then it might actually be secure after all
        if (!request.secure && request.headers.get("x-forwarded-proto") != null) {
            request.secure = request.headers.get("x-forwarded-proto").values.contains("https");
        }

        //redirect if it's not secure
        if (!request.secure) {
            String url = redirectHostHttps() + request.url;
            System.out.println("Redirecting to secure: " + url);
            redirect(url);
        }
    }

    /** Renames the host to be https://, handles both Heroku and local testing. */
    @Util
    public static String redirectHostHttps() {
        if (Play.id.equals("dev")) {
            String[] pieces = request.host.split(":");
            String httpsPort = (String) Play.configuration.get("https.port");
            return "https://" + pieces[0] + ":" + httpsPort; 
        } else {
            if (request.host.endsWith("domain.com")) {
                return "https://secure.domain.com";
            } else {
                return "https://" + request.host;
            }
        }
    }    
}
Ned Twigg
  • 2,159
  • 2
  • 22
  • 38
6

Here is an example that works with Java Play 2.1.1 and Heroku.

public class ForceHttps extends Action<Controller> {

    // heroku header
    private static final String SSL_HEADER = "x-forwarded-proto";

    @Override
    public Result call(Context ctx) throws Throwable {
        final Result result;
        if (Play.isProd() && !isHttpsRequest(ctx.request())) {
            result = redirect("https://" + ctx.request().host()
                    + ctx.request().uri());
        }
        else {
            // let request proceed
            result = this.delegate.call(ctx);
        }
        return result;
    }

    private static boolean isHttpsRequest(Request request) {
        // heroku passes header on
        return request.getHeader(SSL_HEADER) != null
                && request.getHeader(SSL_HEADER)
                        .contains("https");
    }

}

Then to any controllers you want to check for https, add @With(ForceHttps.class). Or if you want all controllers to check, then add a class HttpsController extends Controller and have all your classes extend HttpsController.

e.g.

@With(ForceHttps.class)
public class HttpsController extends Controller {

}
Marcus Ericsson
  • 1,949
  • 1
  • 12
  • 13
  • 1
    This worked without any problem for me on 2.5, with the only difference being that the call method's return type is now CompletionStage, so you can use CompletableFuture.completedFuture to return the redirect. – jyoung Oct 25 '16 at 18:59
3

If your using AWS, you can terminate your HTTPS at the Load Balancer and use a filter to redirect HTTP connection to HTTPS.

AWS Conf:

443 (Load Balancer) ----------> 80 (Server)

80 (Load Balancer) ----------> 80 (Server)

The Filter:

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)
        }
    }
}
Dimitry
  • 4,503
  • 6
  • 26
  • 40
3

I think that you can check in the controller for request.secure == true and then redirect to https.

nylund
  • 1,105
  • 10
  • 15
  • You have Play 1.x in mind? I cannot find any `request.secure` field in Play 2.2. – KajMagnus Jul 13 '14 at 17:16
  • 1
    `request.sequre` is available in Play 2.3, released in June 2014, see the API docs: http://www.playframework.com/documentation/2.3.x/api/scala/index.html#play.api.mvc.RequestHeader – KajMagnus Jul 14 '14 at 13:44
2

You should be able. Do the following:

  1. Set up http.port and https.port in the application.config file
  2. User @@{Controller.action().secure()} when you need to point to a secure page. Use both @@ to generate a full url (including https) and secure to hint to Play you want HTTPS protocol

This should work

Pere Villega
  • 16,429
  • 5
  • 63
  • 100
  • 2
    What if the user types in the address bar the url http ://myapp.org/login instead of https ://myapp.org/login. What I would need would be an https redirection from the controller login() itself. – emt14 Sep 14 '11 at 11:45
0

It does not seem to be possible from the controller point of view. From the template Pere solution works but this only generates the https url from the template.

If the user accesses the login action by either typing manually or following a link to the http url, there does not seem to be a way to enforce/redirect to https.

The best way seems to have a front end proxy.

emt14
  • 4,846
  • 7
  • 37
  • 58