#1406 new
Sander Sõnajalg

Play 1.2.3 / 1.2.4 play.mvc.Router does not fully support proxied SSL

Reported by Sander Sõnajalg | January 31st, 2012 @ 12:02 PM

Framework version: 1.2.3
Platform you're using: Ubuntu 10

Details:

Hi,
I've googled a lot for this issue, and found out that many people have stumbled across it and struggled to solve this in a reasonable way (one similar example is here - http://stackoverflow.com/questions/8512887/xforwardedsupport-for-ht... ). Maybe i'm currently filing a duplicate -- i have no idea. Anyway, i checked 1.2.4 code and this has not yet been fixed. Basically, this problem appears when you use a production setup where you have a front-end server like Apache2 proxying the requests and providing SSL. So this is basically the setup:

[USER] <--- HTTPS --> [APACHE] <--- HTTP ---> [APPSERVER+PLAY]

Now the problem is that when you use Router#getFullUrl, it is giving out URLs with the wrong protocol ("http" instead of "https"). This is because the protocol is derived very naively using the httpRequest#secure property, which in this case will have value "false". Therefore, Router will calculate the protocol to be "http", do what you do. This is for obvious cause, if you see the little-bit-too-naive approach in play.mvc.Http:

        if (port == 80 || port == 443) {
            return String.format("%s://%s", secure ? "https" : "http", domain).intern();
        }

(variable "secure" here is from httprequest.secure field, as mentioned).

This is a terrible showstopper in some situations. For example, I have an app that uses Facebook for user sign-in as one login possibility. Router calculates a url containgin "http" when
the real URL MUST be https for the facebook integration to work. There are other situations where my URLs are programmatically communicated to outter world and have to be correct (start with "https"). Things get especially ugly in my Amazon cloud environment, where the loadbalancer is very hard (impossible?) to configure in a way that it would automatically redirect http port to https.

I currently had to fork play locally to get rid of this issue, but I'd be veryveryvery glad to see a permanent fix : ))) What i currently did is a minor edit it method Router#getFullUrl:

    String base = null;
    String forceBaseUrl = Play.configuration.getProperty("application.forceBaseUrl");

    // This is the patch! We want to be able to force using base URL.
    if (forceBaseUrl != null && "true".equals(forceBaseUrl)) {
        base =  Play.configuration.getProperty("application.baseUrl");
    } else {
        base =  Http.Request.current() == null ? Play.configuration.getProperty("application.baseUrl", "application.baseUrl") : Http.Request.current().getBase();
    }

So having these parameters in application.conf now fix:

application.forceBaseUrl=true
application.baseUrl=https://my.fakesite.com

Thanks a lot! Keep up the good work! :)

Comments and changes to this ticket

  • Sander Sõnajalg

    Sander Sõnajalg January 31st, 2012 @ 03:04 PM

    The other place in your code that has the same issue is play.mvc.results.Redirect#apply. This makes my patch insufficient for now... i'll try hacking the servlet's instance field instead. uhhh...what a mess.

  • Sander Sõnajalg

    Sander Sõnajalg January 31st, 2012 @ 04:27 PM

    I fixed this place as well with another hack (class Redirect)

    // hack hack hack
    public static boolean forceHttps;
    
    static {
        String forceBaseUrl = Play.configuration.getProperty("application.forceHttps");
        if (forceBaseUrl != null && "true".equals(forceBaseUrl)) {
            forceHttps = true;
        }
    }
    

    .. and ..

    public void apply(Request request, Response response) {
        try {
            boolean shouldUseHttps = forceHttps || request.secure;
    
            if (url.startsWith("http")) {
                //
            } else if (url.startsWith("/")) {
                url = String.format("http%s://%s%s%s", shouldUseHttps ? "s" : "", request.domain, (request.port == 80 || request.port == 443) ? "" : ":" + request.port, url);
            } else {
                url = String.format("http%s://%s%s%s%s", shouldUseHttps ? "s" : "", request.domain, (request.port == 80 || request.port == 443) ? "" : ":" + request.port, request.path, request.path.endsWith("/") ? url : "/" + url);
            }
    

    in reality, you need something more proper.
    yuk!!

  • Dan Carley

    Dan Carley February 8th, 2012 @ 11:56 AM

    It's not truly naive. You can parse in a "X-Forwarded-Proto: https" or "X-Forwarded-SSL: on" header to influence the value of "request.secure". You can configure Apache/Nginx/HAproxy to add these headers. Amazon ELBs will provide the former.

    However, the functionality is only enabled when using the "XForwardedSupport" config option, which requires you to know the IP addresses of your reverse-proxy. This isn't practical for some environments like AWS or Heroku (as per SO thread).

    You can see the relevant code from 1.2.4 in the follow link. If testing on a dual-stack machine be sure to hit 127.0.0.1 and not localhost/::1.

    What I'd personally like to see is the three features separated somewhat:

    1. Restricting remote IPs to a predefined list of reverse proxies.
    2. Replacing "request.remoteAddress" with an "XF-For" or "XF-Host" header.
    3. Determining the original request protocol in "request.secure" from an "XF-Proto" or "XF-SSL" header.

    I'm happy to submit a PR if some more people can chip in with their use cases and preferred behaviours.

  • Mirko Adari

    Mirko Adari March 9th, 2012 @ 03:21 PM

    This does not seem to be documented, but you can actually add XForwardedSupport=all to application.conf and it uses both X-Forwarded-Host for rmote address and X-Forwarded-Proto or X-Forwarded-SSL for protocol detection.

  • Dan Carley

    Dan Carley March 9th, 2012 @ 03:24 PM

    Oh, nice catch Mirko. I totally missed that condition at the top. That solves the foremost problem.

Please Sign in or create a free account to add a new ticket.

With your very own profile, you can contribute to projects, track your activity, watch tickets, receive and update tickets through your email and much more.

New-ticket Create new ticket

Create your profile

Help contribute to this project by taking a few moments to create your personal profile. Create your profile »

Play framework

Play makes it easier to build Web applications with Java. It is a clean alternative to bloated Enterprise Java stacks. It focuses on developer productivity and targets RESTful architectures. Learn more on the http://www.playframework.org website.

Source code is hosted on github

Check out our repository at http://github.com/playframework/play

Contributing, creating a patch

Please read the contributor guide

Reporting Security Vulnerabilities

Since all bug reports are public, please report any security vulnerability directly to guillaume dot bort at gmail dot com.

Creating a bug report

Bug reports are incredibly helpful, so take time to report bugs and request features in our ticket tracker. We’re always grateful for patches to Play’s code. Indeed, bug reports with attached patches will get fixed far quickly than those without any.

Please include as much relevant information as possible including the exact framework version you're using and a code snippet that reproduces the problem.

Don't have too much expectations. Unless the bug is really a serious "everything is broken" thing, you're creating a ticket to start a discussion. Having a patch (or a branch on Github we can pull from) is better, but then again we'll only pull high quality branches that make sense to be in the core of Play.

Shared Ticket Bins

People watching this ticket

Pages