Rails redirect with https
Asked Answered
H

10

36

I'm maintaining a Ruby on Rails site and I'm confused as to how to perform redirects to relative URLs using the https protocol.

I can successfully create a redirect to a relative URL using http, for example:

redirect_to "/some_directory/"

But I cannot discern how to create a redirect to a URL using the https protocol. I have only been able to do so by using absolute URLS, for example:

redirect_to "https://mysite.com/some_directory/"

I would like to keep my code clean, and using relative URLs seems like a good idea. Does anyone know how to achieve this in Rails?

Heelandtoe answered 2/11, 2009 at 16:26 Comment(1)
Can I get a clarification on your question. Do you want to force people to always use HTTPS on your site or only for some URLS? Be default RAILS will continue to use the HTTPS if the current request is HTTPS.Alcaeus
V
11

You're probably better off using ssl_requirement and not caring if a link or redirect is or isn't using https. With ssl_requirement, you declare which actions require SSL, which ones are capable of SSL and which ones are required not to use SSL.

If you're redirecting somewhere outside of your Rails app, then specifying the protocol as Olly suggests will work.

Vanessa answered 3/11, 2009 at 2:38 Comment(2)
These days look at force_ssl, available in rails 3.1 and above.Deflate
Matt if you made your comment a separate answer, I'd vote for it.Cami
I
41

The ActionController::Base#redirect_to method takes an options hash, one of the parameters of which is :protocol which allows you to call:

redirect_to :protocol => 'https://', 
            :controller => 'some_controller', 
            :action => 'index'

See the definition for #redirect_to and #url_for for more info on the options.


Alternatively, and especially if SSL is to be used for all your controller actions, you could take a more declarative approach using a before_filter. In ApplicationController you could define the following method:

def redirect_to_https
    redirect_to :protocol => "https://" unless (request.ssl? || request.local?)
end

You can then add filters in your those controllers which have actions requiring SSL, e.g:

class YourController
    before_filter :redirect_to_https, :only => ["index", "show"]
end

Or, if you require SSL across your entire app, declare the filter in ApplicationController:

class ApplicationController
    before_filter :redirect_to_https
end
Incapacious answered 3/11, 2009 at 0:3 Comment(2)
You can use :protocol => 'https://' iff you are passing a hash to redirect_to if you are passing a relative URL like in the question above, that will not work. I agree that a before_filter is the better way to go in general.Elmiraelmo
Be careful with this, the redirect will drop all the parameters. If you wants to redirect JUST FOR the protocol, you should call like this: redirect_to({:protocol => "https://"}.merge(request.parameters))Mckelvey
V
38

If you want your entire application to be served over https then since Rails 4.0 the best way to do this is to enable force_ssl in the configuration file like so:

# config/environments/production.rb
Rails.application.configure do
  # [..]

  # Force all access to the app over SSL, use Strict-Transport-Security,
  # and use secure cookies.
  config.force_ssl = true
end

By default this option is already present in config/environments/production.rb in in newly generated apps, but is commented out.

As the comment says, this will not just redirect to https, but also sets the Strict-Transport-Security header (HSTS) and makes sure that the secure flag is set on all cookies. Both measures increase the security of your application without significant drawbacks. It uses ActionDispatch:SSL.

The HSTS expire settings are set to a year by default and doesn't include subdomains, which is probably fine for most applications. You can configure this with the hsts option:

config.hsts = {
  expires: 1.month.to_i,
  subdomains: false,
}

If you're running Rails 3 (>=3.1) or don't want to use https for the entire application, then you can use the force_ssl method in a controller:

class SecureController < ApplicationController
  force_ssl
end

That's all. You can set it per controller, or in your ApplicationController. You can force https conditionally using the familiar if or unless options; for example:

# Only when we're not in development or tests
force_ssl unless: -> { Rails.env.in? ['development', 'test'] }
Vigilante answered 15/10, 2014 at 12:6 Comment(4)
force_ssl is not a great option if you are developing behind a tunnel. For example, an ngrok tls tunnel allows you to specify a cert. Then ngrok terminates the tls connection with the specified cert and forwards unencrypted to your app (so your app thinks everything is http). This means you don't have to setup an nginx proxy and do https on your development box.Gilliangilliard
@MichaelJohnston This is why you can use unless to not force SSL in dev (see the last line in my answer).Vigilante
@MichaelJohnston this should only be turned on in the production environment and will not impact your local development setupVex
AWS allows you to terminate SSL at the load balancer. Which then forwards unencrypted data to the EC2 instances (where your app is deployed). Unfortunately, force_ssl isnt a good option here, even with the unless condition specified aboveWrongdoer
V
11

You're probably better off using ssl_requirement and not caring if a link or redirect is or isn't using https. With ssl_requirement, you declare which actions require SSL, which ones are capable of SSL and which ones are required not to use SSL.

If you're redirecting somewhere outside of your Rails app, then specifying the protocol as Olly suggests will work.

Vanessa answered 3/11, 2009 at 2:38 Comment(2)
These days look at force_ssl, available in rails 3.1 and above.Deflate
Matt if you made your comment a separate answer, I'd vote for it.Cami
S
3

If you want to globally controll the protocol of urls generated in controllers, you can override the url_options method in you application controller. You could force the protocol of the generated urls depending on the rails env like so :

 def url_options
    super
    @_url_options.dup.tap do |options|
      options[:protocol] = Rails.env.production? ? "https://" : "http://"
      options.freeze
    end
  end

this example works in rails 3.2.1, i'm not exactly sure for earlier or future versions.

Strainer answered 6/3, 2012 at 11:19 Comment(0)
G
3

This answer is somewhat tangential to the original question, but I record it in case others end up here in similar circumstances to myself.

I had a situation where I needed to have Rails use https proto in url helpers etc. even though the origin of all requests is unencrypted (http).

Now, ordinarily in this situation (which is normal when Rails is behind a reverse proxy or load balancer etc.), the x-forwarded-proto header is set by the reverse proxy or whatever, so even though requests are unencrypted between the proxy & rails (probably not advisable in production by the way) rails thinks everything is in https.

I needed to run behind an ngrok tls tunnel. I wanted to have ngrok terminate the tls with letsencrypt certificates I specified. However when it does so, ngrok does not offer the ability to customize headers, including setting x-forwarded-proto (although this feature is planned at some point in the future).

The solution turned out to be quite simple: Rails does not depend on either the protocol of the origin or whether x-forwarded-proto is set directly, but on the Rack env var rack.url_scheme. So I just needed to add this Rack middleware in development:

class ForceUrlScheme
  def initialize(app)
    @app = app
  end

  def call(env)
    env['rack.url_scheme'] = 'https'
    @app.call(env)
  end
end
Gilliangilliard answered 24/4, 2017 at 19:51 Comment(0)
K
3

If you want to force ALL traffic via https, then the best way in Rails 6 is to configure production.rb with:

config.force_ssl = false

If you need a more flexible solution, you can handle it with a simple before_action filter:

class ApplicationController < ActionController::Base

  include SessionsHelper
  include LandingpageHelper
  include ApplicationHelper
  include UsersHelper
  include OrganisationHelper

  before_action :enforce_ssl, :except => [:health] 

  def enforce_ssl
    if ENV['ENFORCE_SSL'].to_s.eql?('true') && !request.ssl?
      redirect_to request.url.gsub(/http/i, "https")
    end
  end 


end

If you run your application on AWS ECS Fargate with health checks, then you need a more flexible solution because the health check from the AWS target group is not invoked via https. Of course, you want the health check to work and at the same time, you want to force SSL for all other controller methods.

The ENFORCE_SSL is just an environment variable that turns this feature on/off.

Keep answered 25/10, 2020 at 15:38 Comment(1)
If it is just for health checking or metrics endpoints, you an still use config.force_ssl = true and then use config.ssl_options = { redirect: { exclude: -> request { /healthcheck/.match?(request.path) } } } in order to exclude requests to the healthcheck from redirection to SSL.Anorthite
K
2

In Rails 4 one can use the force_ssl_redirect before_action to enforce ssl for a single controller. Please note that by using this method your cookies won't be marked as secure and HSTS is not used.

Kiki answered 14/4, 2015 at 10:59 Comment(1)
Can you please elaborate on the last sentence? For someone unfamiliar with 'marking cookies as secure' and HSTS.Sldney
E
0

Add protocol to ..._url:

redirect_to your_url(protocol: 'https')

or with subdomain:

redirect_to your_url(protocol: 'https', subdomain: 'your_subdomain')
Eyespot answered 16/8, 2021 at 17:38 Comment(0)
E
-2

Relative URLs, by definition, use the current protocol and host. If you want to change the protocol being used, you need to supply the absolute URL. I would take Justice's advice and create a method that does this for you:

def redirect_to_secure(relative_uri)
  redirect_to "https://" + request.host + relative_uri
end
Elmiraelmo answered 2/11, 2009 at 17:48 Comment(0)
D
-3

Open the class that has redirect_to and add a method redirect_to_secure_of with an appropriate implementation. Then call:

redirect_to_secure_of "/some_directory/"

Put this method in the lib directory or somewhere useful.

Dorsiventral answered 2/11, 2009 at 16:30 Comment(2)
Thanks, but I'm looking for a little more detail than "an appropriate implementation."Near
Sorry, I thought you were asking how to make the calling code clean.Dorsiventral

© 2022 - 2024 — McMap. All rights reserved.