Let’s imagine that you need to create a secure login area, for example if you own http://www.domain.com you may want to put the user login page on https://secure.domain.com. How can you do it?
The best solution in my opinion is to put all the login procedures in a dedicated login controller (eg the ‘sessions’ controller if you are using the RESTful authentication plugin) and then modify a bit the url_for method in order to modify the calls to that controller.
Part 1, A little configuration.
In our environment.rb we’re going to create an array with the name of the controllers we want to put under a different subdomain and under SSL:
SECURE_MODE = [ /\/sessions$/ ].freeze
SECURE_SUBDOMAIN = "secure".freeze
Part 2, Url_for tweaking
Then in our application.rb we have to tweak a bit the url_for function in order to let the defined controllers being handled correctly.
def url_for(options = {}, *parameters_for_method_reference)
tmp_result = super((options.is_a?(Hash) ? options.merge({:only_path => false})
: options), parameters_for_method_reference)
if not (tmp_result =~ Regexp.new(request.host)).nil?
# this tweak is necessary just beacuse I use WebBrick for my development environment (aka I need to specify the port)
if ENV['RAILS_ENV'] == ‘production’
cut_result = tmp_result.to_s.gsub( Regexp.new("#{request.protocol}# {request.host}") ,"" )
elsif ENV['RAILS_ENV'] == ‘development’
cut_result = tmp_result.to_s.gsub( Regexp.new("#{request.protocol}# {request.host_with_port}"), "" )
end
# We’re going to talk about this function later
url_to_go = dispatch_ssl(cut_result)
end
super((url_to_go.nil? ? tmp_result : url_to_go), parameters_for_method_reference)
end
Part 3 Dispatching urls
dispatch_ssl is a function I wrote that receive a partial url (eg /session) and returns the path modified in order to match the new subdomain and protocol rules.
def dispatch_ssl(tmp_result = request.request_uri)
subdomain = SECURE_SUBDOMAIN.blank? ? "" : SECURE_SUBDOMAIN + "."
# need secure is a function that simply check if this path need to be put under SSL
if need_secure?(tmp_result)
if request.protocol == ‘http://’
url_to_go = "https://#{subdomain}" + request.host + tmp_result
end
else
if request.protocol == ‘https://’
host = request.host.gsub(Regexp.new(subdomain),"")
url_to_go = ‘http://’ + host + tmp_result
end
end
return url_to_go
end
Last but not Least: need_secure? and ssl_require
The first function is used by dispatch_ssl to check if the current url match the SECURE_MODE array, the second one (ssl_require) have to be called in a before_filter directive and detects if the user is trying to access a secure area without the correct subdomain and protocol; if this happens the user will be redirected to the correct url.
def need_secure?(uri)
SECURE_MODE.each{ |s|
a = (uri =~ s)
return true unless a.nil?
}
return false;
end
def ssl_required
if not (redir = dispatch_ssl.to_s).blank?
redirect_to(redir) and return false
end
end