Search code examples
javanginxplayframeworkplayframework-2.2

PlayFramework return absolute url in http instead of httpS?


I've implemented a project in Play!Framework with NGinx using only https.

Everything works fine, the SSL is well recognized and I can use my app from anywhere but when Play! returns an absolute URL, it's in http, not https.

This is problematic, and I don't know where the problem is. I tried to start Play with -Dhttps.port=XXXX instead of -Dhttp.port=XXXX but it didn't changed the output of "http" instead of "https".

I'm suspecting an Nginx bad configuration (a parameter I forgot?). Here's my sites-enabled/website config file :

proxy_buffering    off;
proxy_set_header   X-Real-IP $remote_addr;
proxy_set_header   X-Scheme "https"; # I also tried $scheme without any luck
proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header   Host $http_host;
proxy_http_version 1.1;

server {
        listen 80;
        server_name my.website.com;
        return      301 https://my.website.com;
}

upstream my-backend {
        server 127.0.0.1:9100;
}

server {
    listen               443;
    ssl                  on;
    root                 /var/www/website/errors/;

    # http://www.selfsignedcertificate.com/ is useful for development testing
    ssl_certificate      /etc/nginx/ssl/my.website.com.crt;
    ssl_certificate_key  /etc/nginx/ssl/my.website.com.key;

    # From https://bettercrypto.org/static/applied-crypto-hardening.pdf
    ssl_prefer_server_ciphers on;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2; # not possible to do exclusive
    ssl_ciphers 'EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:+CAMELLIA256:+AES256:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!ECDSA:CAMELLIA256-SHA:AES256-SHA:CAMELLIA128-SHA:AES128-SHA';
    add_header Strict-Transport-Security max-age=15768000; # six months
    # use this only if all subdomains support HTTPS!
    # add_header Strict-Transport-Security "max-age=15768000; includeSubDomains"

    keepalive_timeout    70;
    server_name my.website.com;

    location / {
        #proxy_pass  http://my-backend;
        proxy_pass  http://127.0.0.1:9100;
    }

    location ~ /\.git {
        deny all;
    }

    error_page 502 @maintenance;
    location @maintenance {
        rewrite ^(.*)$ /error502.html break;
    }
}

What am I missing?

Update: Here's the code that generate the absolute url :

controllers.routes.Pages.loginToken(getToken()).absoluteURL(play.mvc.Http.Context.current().request());

Solution

  • There are a couple overloads for absoluteURL. You're using this one:

    public String absoluteURL(Http.Request request) {
        return absoluteURL(request.secure(), request.host());
    }
    

    The problem with this is that since you're reverse proxying to Play via nginx, Play is actually receiving all the requests through HTTP, and not HTTPS. This means that request.secure() is false, and absoluteURL will return a URL containing http://....

    Instead, manually set secure to true in one of the overloads:

    controllers.routes.Pages.loginToken(getToken()).absoluteURL(play.mvc.Http.Context.current().request(), true);
    

    Additionally, what I normally do is have a configuration variable for secure so it can generate non-https URLs when developing locally.

    In application.conf:

    application.secure = false # for local dev
    

    And in production I add the command line option -Dapplication.secure=true when starting the application, to override the value in application.conf.

    Then generating the URL would look like this:

    controllers.routes.Pages.loginToken(getToken()).absoluteURL(
        play.mvc.Http.Context.current().request(), 
        play.Play.application().configuration().getBoolean("application.secure", true) // default to true
    );