Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails application behind proxy, with SSL, renders paths as "http://"

To start with, this sounds more like a bug then anything else.

My rails application is served by Unicorn. Then, using Nginx as a reverse proxy, I serve the application to the outside world using SSL.

So far so good, no problem. I'm using relative paths (Restful path helpers), so there should be no problem to produce this (for https://www.example.com):

new_entry_path => https://www.example.com/entries/new

This works fine in most cases.

The problem however appears when in a controller I try to redirect to a "show" action (using resources), let's say after a successful update (suppose Entry with id 100):

redirect_to @entry, flash: {success: "Entry has been updated"}

or

redirect_to entry_path(@entry), flash: {success: "Entry has been updated"}

they both produce a redirect to:

http://www.example.com/entries/100 # missing 's' in https...

instead of

/entries/100 # implying https://www.example.com/entries/100

As far as I've noticed, this only happens with show action and only in controller redirects.

I'm bypassing this by doing something horrible and disgusting:

redirect_to entry_url(@entry).sub(/^http\:/,"https:"), flash: {success: "Entry has been updated"}

Has anyone ever confronted something similar? Any ideas will be gratefully accepted...

like image 522
Ruby Racer Avatar asked Jan 09 '23 17:01

Ruby Racer


2 Answers

I had a similar problem. By default Rails will use the current protocol for the *_url helpers.

We use nginx as web server and unicorn as application server. Nginx takes the request, unwrapps the SSL part and then passes it on to unicorn. Hence, unicorn always receives a http request. If we now want Rails to know about the original protocol, we can need to add the X-Forwarded-Proto header.

Example config:

upstream app {
    server unix:/var/run/myserver/unicorn.sock fail_timeout=0;
}
server {
    listen       443 ssl;
    server_name  myserver;

    ssl_certificate "/etc/pki/nginx/myserver.crt";
    ssl_certificate_key "/etc/pki/nginx/private/myserver.key";

    root /myserver/public;

    try_files $uri/index.html $uri @app;
    sendfile on;

    location @app {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https; # <--- will be used
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://app;
   }
}
like image 138
Motine Avatar answered Jan 17 '23 17:01

Motine


I think force_ssl is what you are looking for.

class AccountsController < ApplicationController
  force_ssl if: :ssl_configured?

  def ssl_configured?
    !Rails.env.development?
  end
end

Edit, if you really want to do redirects to relative paths you could always create your own helper:

module ActionController
  module RelativeRedirectingHelper
    extend ActiveSupport::Concern

    include AbstractController::Logger
    include ActionController::RackDelegation
    include ActionController::UrlFor

    def redirect_to_relative(path, response_status = 302) #:doc:
      raise ActionControllerError.new("Cannot redirect to nil!") unless options
      raise ActionControllerError.new("Cannot redirect to a parameter hash!") if options.is_a?(ActionController::Parameters)
      raise AbstractController::DoubleRenderError if response_body

      self.status        = response_status
      self.location      = path
      self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>"
    end
  end
end

This is a quick'n'dirty copy paste job . Will take a little more effort if you want have the same signature as redirect_to

like image 38
max Avatar answered Jan 17 '23 18:01

max