views:

77

answers:

2

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?

A: 

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.

Matt
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.
Daniel
+1  A: 

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).

Samuel
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.
Daniel
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.
Samuel
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"}
Daniel
Found a solution. Remove `scope :protocol => 'http://'` from your routes and add the helper in my edit.
Samuel
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?
Daniel
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).
Samuel