I find that when I run Rails 5 behind a reverse proxy, the default behaviour of url_for is use the host name of the upstream server, not the proxy itself.
For example, with nginx running at front.com and proxying requests to my upstream Rails server, which it knows as back, root_url returns http://back/.
If I tell nginx to pass the Host header as it proxies, root_url is http://front.com.
So far, so good. But I find using default_url_options in routes.rb (example below) does not have the power to override this. Why not? And if default_url_options can't override the Hostname from the request, what's the point of it?
Thanks!
EDIT
default_url_options example:
RoutingApp.routes.draw do
default_url_options host: 'example.com'
root to: 'pages#index'
end
(from this gist by a Rails core developer)
There are many places to set this, you set it for it to work under console, but not controller.
http://lulalala.logdown.com/posts/5835445-rails-many-default-url-options
I think you want it to work for controller/web view. Try this:
# production.rb
config.action_controller.default_url_options= {host: 'example.com'}
I'm successfully running subdomains on my mac OSX as outlined by Robby on Rails. I'd like to follow the example code below but I have no idea where to start. How do you edit a DNS setup? Do I need to have a dedicated virtual host to do this? Right now I have a standard (crappy) Dreamhost shared hosting. If I go with a DVH, is Media Temple a good pick?
Any media recs would be helpful - my google search's have only resulted in angry customer/hosting company tirades.
Thanks!
# 1) Point *.example.com in your DNS setup to your server.
#
# 2) Setup an Apache vhost to catch the star pointer:
#
# <VirtualHost *:80>
# ServerName example.com
# ServerAlias *.example.com
# </VirtualHost>
#
# 3) Set the current account from the subdomain
class ApplicationController < ActionController::Base
before_filter :set_current_account
private
def set_current_account
#current_account = Account.find_by_subdomain!(request.subdomains.first)
end
end
# 4) Tie all top-level requests off the current account
class CustomersController < ApplicationController
def index
#customers = #current_account.customers
end
end
I do something similar with Heroku, which supports Wildcard DNS, definitely worth a look. There are a range of options for handling the DNS - most of the time this is actually handled by whoever is managing your name servers.
Dreamhost will allow Wildcard DNS hosting on request ... basically, they handle mapping incoming requests using their name servers, so not much for you to do other than send an email.
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.
Multiple customer instances of an application, under a single application.
What I need is to allow multiple users, to connect to my Apache Web server, by passing different url like :
customer1.myhost.com
company1.myhost.com
company2.myhost.com
etc.
What I want my Apache server to do, is pass all request that are not directed to a certain list of existing hosts
(like trac.myhost.com and https://myhost.com) over to my Rails application, by setting a RequestHeader to identify the requested host, something like :
RequestHeader "INSTANCE_NAME" = customer1 #for customer1.myhost.com
Thanks for your help!
Ps.: The end goal is to offer software slices as a service, but having all those customers managed under 1 application running. Not 1 app per customer.
Using a standard VirtualHost configuration you can do this:
NameVirtualHost *:80
<VirtualHost *:80>
ServerName app.example.com
ServerAlias *.example.com
DocumentRoot /web/app.example.com/public
</VirtualHost>
This will capture all requests that are not already captured by other VirtualHost entries.
When your application receives the request, you will have the request variable set with the host-name provided. This is available to any ActionController:
request.host
From there you can load the appropriate data in some kind of before_filter, as is typically done like:
before_filter :load_client
def load_client
#client = Client.find_by_hostname!(request.host)
rescue ActiveRecord::RecordNotFound
render(:partial => 'client_not_found', :status => :not_found)
end
So long as the client has the hostname populated correctly, this will find them on each page load.
I want to point several domain names to the same Rails application. The content is different for each domain, but the functionality and the structure of the application is the same.
What is the best way to do this when it comes to server set up and routing? I will use nginx as a web server.
if layout needs to be changed only:
add to application controller
layout :setup_layout
def setup_layout
if request.host == "site1.host.tld"
"layout1"
else
"layout2"
end
end
the same logic you can use to get content, this is true if all sites will use one database.
In nginx conf add more hosts to server_name directive:
server_name site1.host.tld site2.host.tld