Rails 3 SSL routing redirects from https to http
Asked Answered
E

2

43

This question relates to this SO question and answer (rails-3-ssl-deprecation ) where its suggested to handle ssl in rails 3 using routes.rb and routes like:

resources :sessions, :constraints => { :protocol => "https" }

# Redirect /foos and anything starting with /foos/ to https.
match "foos(/*path)", :to => redirect { |_, request|  "https://" + request.host_with_port + request.fullpath }

My problem is that links use relative paths(i think thats the correct term) and once I'm on a https page all the other links to other pages on the site then use https.

1) Whats the best way to get back to http for pages where https isn't required? Do I have to setup redirects for all them(I hope note) or is there a better way. Would the redirects be like this:

match "foos(/*path)", :to => redirect { |_, request|  "http://" + request.host_with_port + request.fullpath }

2) If redirects back to http are required, how do I handle a case where I want all methods to be http except one? ie foos(/*path) would be for all foos methods. But say I wanted foos/upload_foos to use ssl. I know how to require it

scope :constraints => { :protocol => "https" } do
  match 'upload_foos' => 'foos#upload_foos', :via => :post, :as => :upload_foos 
end

but if I put in the http redirect to the foos path what happens to https upload_foos?

Exserviceman answered 22/10, 2010 at 3:18 Comment(0)
S
44

If you want all your links to be able to switch between http and https, you have to stop using the _path helper and switch to _url helpers.

After that, using a scope with the protocol parameter forced and protocol constraint makes the urls automatically switch.

routes.rb
scope :protocol => 'https://', :constraints => { :protocol => 'https://' } do
  resources :sessions
end

resources :gizmos

And now in your views:

<%= sessions_url # => https://..../sessions %>
<%= gizmos_url   # => http://..../gizmos %>

Edit

This doesn't fix urls that go back to http when you are in https. To fix that you need to override url_for.

In any helper
module ApplicationHelper
  def url_for(options = nil)
    if Hash === options
      options[:protocol] ||= 'http'
    end
    super(options)
  end
end

This will set the protocol to 'http' unless it was explicitly set (in routes or when calling the helper).

Supinator answered 22/10, 2010 at 14:28 Comment(10)
Thanks very much for the reply. For me its working going from http to https but not back again. Using your example: when on the http gizmos page, all links are http other than sessions which is https which is correct. If I go to the https sessions page all other links become https including the links to gizmos, even though all use the _url helper. Any ideas? I'm also not sure if I like having to use the _url helper for all links on the website when only a few require https.Exserviceman
Yeah, you also need to add scope :protocol => 'http://' do end around all your other routers. That way the helpers use http instead of https. You have to use the _url helpers because they generate a full url where as the _path helpers only generate a path.Supinator
When I do that none of the routes work other than when on the root page. root :to => "gizmos#index". If I'm on that root page all the links appear correct but on any of the other pages http or https I now get routing errors when it tries to create links with the url helper. eg. No route matches {:controller=>"gizmos", :action=>"create"}Exserviceman
Found a solution. Remove scope :protocol => 'http://' from your routes and add the helper in my edit.Supinator
That works, thanks Samuel. One last problem I have is when I go to my home page(root :to => "gizmos#index") I get redirected to a https login page, which is how I want it. After logging in I'm then taken to the root page but its https rather than http. Is there a way to write the root route that makes it http?Exserviceman
Easiest solution is to just use :protocol => 'http' with your redirect after logging in. Otherwise you will have to override the same url_for method in ActionController::Base like we did for your views (which is why the views urls switch back to http but urls in your controller don't).Supinator
Thanks, I think this is what I need though the wholesale changes from _path to _url makes me nervous, there are tons of files that needed to be changed. Do I have make the same changes for form and redirect using _path? I don't think so since they aren't used with url_for. Also I would be making changes in some vendor files like devise views.Termor
Another thought occurred to me, is it possible to override '_path' method to convert them into urls and hence achieve the same objective without the need for wholesale changes.Termor
I wish Rails made this easier. I'd like to not have to override url_for in the helpers and the controllers.Gardol
This is the best answer I've come across so far. However, it kind of breaks the development environment, since there's not a certificate there; is there a way to do exactly this, but if development, not? Or is installing a local self-signed certificate the only option?Lobo
M
0

This was a long time ago and I'm sure it can be improved, but back on some old version of rails I had this code in application controller. Not sure this is still valid for Rails 3, but it may be of some help:

private
  SECURE_ACTIONS = {
    :login => ["login", "login_customer", "remind_password", "add_customer", "add_or_login_customer"], 
    :store => ["checkout", "save_order"],
    :order => ["show"] }

  # Called as a before_filter in controllers that have some https:// actions
  def require_ssl
    unless ENV['RAILS_ENV'] != 'production' or  @request.ssl?
      redirect_to :protocol => 'https://', :action => action_name
      # we don't want to continue with the action, so return false from the filter
      return false
    end
  end

def default_url_options(options)
    defaults = {}    

    if USE_EXPLICIT_HOST_IN_ALL_LINKS
      # This will OVERRIDE only_path => true, not just set the default.
      options[:only_path] = false
      # Now set the default protocol appropriately:
      if actions = SECURE_ACTIONS[ (options[:controller] || controller_name).to_sym ] and 
         actions.include? options[:action]

        defaults[:protocol] = 'https://'
        defaults[:host] = SECURE_SERVER if defined? SECURE_SERVER
      else
        defaults[:protocol] = 'http://'
        defaults[:host] = NON_SECURE_SERVER if defined? NON_SECURE_SERVER
      end
    end
    return defaults
  end

The USE_EXPLICIT_HOST_IN_ALL_LINKS was some global configuration option, but you can ignore this.

In each controller that required https, I'd add before_filter :require_ssl and add that controller name and its methods to SECURE_ACTIONS. This probably can be improved by passing the action names to the before filter, or something.

Musgrove answered 22/10, 2010 at 11:30 Comment(1)
Thanks for your reply. I like how you can specify all the secure actions in one place. It makes it nice and clear whats secured. I'll be sure to give it a go in my rails 3 app if the other answer doesn't work out.Exserviceman

© 2022 - 2024 — McMap. All rights reserved.