Force SSL for http requests in Rails - ruby-on-rails

I'm working on a platform built with Ruby(v2.1.2) on Rails(v4.1.6) and we're trying to enforce SSL. How do I go about doing this?
So far, I have force_ssl
# application_controller.rb
force_ssl if: :ssl_configured?
def ssl_configured?
return false if params[:controller] == 'high_voltage/pages'
(Rails.env.development? || Rails.env.test?) ? false : true
end
Which seems to work because when I try to do http://www.somecoolsite.com, it then automatically becomes https://www.somecolesite.com.
However, if I try to submit a JSON post request to the API portion of our platform, and the URL is http://, the post request is somehow returning the results of a get request instead. But when I change the URL to https://, the post request works as expected. How would I go about fixing this so that if a client accidentally submits their request as http://, it is rewritten to https://?
Thanks!

Have you looked at using config.force_ssl = true in your production environment config? This is far more all-encompassing, but can lead to other issues as well.

Related

Force Ruby on Rails app to load on HTTPS version

I have an HTTP and HTTPS version of my Ruby on Rails running, and if someone accesses through the HTTP one, I want to automatically reload the HTTPS version.
I have done this before on Apache with .httaccess. How can I do it on Rails?
Thanks
In your production.rb file (or whichever env you are forcing ssl) add
config.force_ssl = true
You can also achieve that by:
class ApplicationController < ActionController::Base
force_ssl if: :ssl_enabled?
private
def ssl_enabled?
%w(staging production).include?(Rails.env)
end
end
You can achieve it by putting the below code in config/applcation.rb
#config/application.rb
config.force_ssl = true
Check this blog and this SO post for more info.

Trying to force ssl, getting "This webpage has a redirect loop"

I'm trying to force all urls generated by my controller to be "https" and redirecting to https otherwise. I tried the method explained in this blog and the one suggested by Olly here
and I'm getting this error if I try to access my application:
This webpage has a redirect loop
The webpage at https://'example.com' has resulted in too many redirects. Clearing
your cookies for this site or allowing third-party cookies may fix the problem.
If not, it is possibly a server configuration issue and not a problem with
your computer.
Clearing the cookie doesn't help. Why is it going into a loop? I made sure to run the code under condition that request.ssl is false. I'm using Rails 2.3.10.
So here is what I tried, in my application controller I added:
url_for unless (Rails.env.test? || Rails.env.development?)
def default_url_options(options)
{ :only_path => false, :protocol => 'https' } unless (Rails.env.test? || Rails.env.development?)
end
I also tried
def redirect_to_https
redirect_to :protocol => "https://" unless (request.ssl? || Rails.env.test? || Rails.env.development? || local_request?)
end
The domain I'm using is shared among different apps so the request is received by the apache server and is forwarded to the app server so enforcing ssl in this way doesn't work because the app sees the request as "local" and always over http so it goes into a redirect loop. I will enforce https on the server level instead.

trying to force a redirect to https from application controller [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Why am I getting infinite redirect loop with force_ssl in my Rails app?
I have this in my application controller:
before_filter :redirect_to_ssl!
def redirect_to_ssl!
redirect_to "https://" + request.host + request.fullpath if !request.ssl? && Rails.env.production?
end
However, when I view my site (in production mode) with https:// it goes in a redirect loop... It appears that request.ssl? returns false even though I am using the https protocol..... Now, when I view my site, the browser does warn me that my ssl certificate can't be verified-- I am wondering if that is why request.ssl? returns false? What is a good workaround? Should I just check to see if the request.protocol == "https://" ?
Don't do this in the controller.
Instead, in config/environments/production.rb (and whichever other environments you want to use SSL), do this:
config.force_ssl = true
Read more info on configuring Rails 3 to use HTTPS.

Rails request forgery protection settings

please help a newbie in Rails :) I have protect_from_forgery call (which is given by default) with no attributes in my ApplicationController class.
Basically here's the code:
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
protect_from_forgery
helper_method :current_user_session, :current_user
filter_parameter_logging :password, :password_confirmation
What I assume it should do is: it should prevent any POST requests without correct authenticity_token. But when I send post request with jQuery like the one below, it works fine (there's update statement that is executed in the database)!
$.post($(this).attr("href"), { _method: "PUT", data: { test: true } });
I see in console that there's no authenticity_token among sent parameters, but request is still considered valid. Why is that?
UPD
Found config setting in config/environments/development.rb
config.action_controller.consider_all_requests_local = true
Because of the DEV environment and local requests, these jQuery post requests were OK.
There is nothing wrong with your code as long as the request $.post($(this).attr("href"), { _method: "PUT", data: { test: true } }); is executed from within the app itself. If you had another app running elsewhere, say for example on localhost:3001, and you sent a post from there then it won't work. Infact if you are on firefox > 3.0 it has an early implementation of cross site xhr too. For example you can send a POST from any other site (but this works provided protect_from_forgery is turned off!). The reason why auth token is not necessary for xhr is that cross site xhr is disabled. So it is safe to use xhr without providing auth token. If you try from any where else other than your app, i am sure it will raise an exception asking for an auth token. Also you should have a crossdomain.xml defined to prevent access from outside sources.
Try doing this: curl -X -d url_endpoint_of_your_app. See if you get a 200 response code. If you do then there is something fishy.
Silly question, perhaps: Are you sure you're subclassing ApplicationController? What's your route map look like? And what version of Rails (just for clarity)?
Did you verify that the call from jQuery is actually a POST and not a GET? (I know, it seems obvious). Rails will only perform the protection on non-GET requests.
Also, what is the content-type of the request that's going out. Rails will also only perform the protection, according to the docs, if it's an HTML/Javascript request.

Rails redirect with https

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?
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
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'] }
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.
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.
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
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.
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.
Add protocol to ..._url:
redirect_to your_url(protocol: 'https')
or with subdomain:
redirect_to your_url(protocol: 'https', subdomain: 'your_subdomain')
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
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.

Resources