SSL-specific hostname in Heroku with Rails 3.1 - ruby-on-rails

I currently have a setup where I force SSL or http where I need it with this before_filter in my application controller:
def force_ssl
if params[:controller] == "sessions"
if !request.ssl? && Rails.env.production?
redirect_to :protocol => 'https://', :status => :moved_permanently
end
else
if request.ssl? && Rails.env.production?
redirect_to :protocol => 'http://', :status => :moved_permanently
end
end
end
What I'd like to do is to use https://secure.example.com when using SSL but keep using http://example.com when not using SSL. Is there a way I can switch between the hostnames depending on whether I'm using SSL?

First I'll show how to force SSL in current and earlier versions of Rails, then at the end I've posted how to use HTTP and HTTPS in Parallel with each other, which is what I think your looking for.
Rails >= 3.1
Simply use config.force_ssl = true in your environment configuration.
# config/application.rb
module MyApp
class Application < Rails::Application
config.force_ssl = true
end
end
You can also selectively enable https depending on the current Rails environment. For example, you might want to keep HTTPS turned off on development, and enable it on staging/production.
# config/application.rb
module MyApp
class Application < Rails::Application
config.force_ssl = false
end
end
# config/environments/production.rb
MyApp::Application.configure do
config.force_ssl = true
end
Rails < 3.1
Just in case you have any projects that are not Rails 3.1 and want the same feature. Enable HTTPS by adding the following line to your environment configuration.
config.middleware.insert_before ActionDispatch::Static, "Rack::SSL"
Note that I’m passing Rack::SSL as string to delegate the loading of the class at the end of the Rails application initialization. Also note the middleware must be inserted in a specific position in the stack, at least before ActionDispatch::Static and ActionDispatch::Cookies.
Don’t forget to define Rack::SSL dependency in your Gemfile.
# Gemfile
gem 'rack-ssl', :require => 'rack/ssl'
Enabling HTTPS and HTTP in parallel
Rack::SSL has a very interesting and undocumented feature. You can pass an :exclude option to determine when to enable/disable the use of HTTPS.
The following code enables Rack::SSL and all its filters only in case the request comes from a HTTPS connection.
config.middleware.insert_before ActionDispatch::Static, Rack::SSL, :exclude => proc { |env| env['HTTPS'] != 'on' }
Both the following URLs will continue to work, but the first one will trigger the Rack::SSL filters.
https://secure.example.com
http://example.com

Related

Rails app GET routes not working in production mode

I've inherited a Rails application that isn't completely functional when running in production mode. GET requests to the server are resulting in a no route match found error; however, when the server is run in development mode all routes will work, giving an expected 200 status.
Reviewing the code reveals that the application expects a prefixed subdomain in addition to the domain used in a successful URL request.
class ApplicationContext
def initialize(subdomain, host_with_port)
if subdomain.present?
case subdomain
when Rails.application.config.workninja.admin_subdomain
#environment = :admin
when Rails.application.config.workninja.mobile_subdomain
#environment = :mobile
else
#environment = :customer
end
else
raise 'Could not initialize ApplicationContext without subdomain'
end
#url_options = {subdomain: subdomain, host: host_with_port}
setup_method = "setup_#{#environment.to_s}_environment".to_sym
if respond_to?(setup_method, true)
send(setup_method, subdomain, host_with_port)
else
raise 'Unknown context environment'
end
end
attr_reader :environment
attr_reader :url_options
attr_reader :agency
def self.current
Thread.current['workninja:tenant_context']
end
def admin?
#environment == :admin
end
def mobile?
#environment == :mobile
end
def customer?
#environment == :customer
end
def ui_url_for(path, subdomain = url_options[:subdomain])
base = "#{Rails.application.config.workninja.https ? 'https' :
'http'}://#{subdomain}.#{Rails.application.config.workninja.domain}"
if Rails.application.config.workninja.html5mode
puts URI.join(base, path).to_s
else
puts URI.join(base, "/#/#{path}".gsub(/\/+/, '/')).to_s
end
end
The original front end supplied with the application constructs the request's URLs depending on the environment the sever was launched in.
{
"environment": "development",
"url": "http://admin.workninja.local:3000"
}
{
"environment": "production",
"url": "/api"
}
To me the production URL doesn't make sense as all it does is append "/api" to the root domain that the front end is hosted on. I can only assume that it's just a placeholder that needs to be replaced with the domain name that the rails server is hosted on once it's running in a live environment. The "/api" path isn't used throughout the functional development version of the app which makes me further assume it's a placeholder.
Using the above as a guide I replaced "/api" with "http://admin.workninja.com.au". After hosting the application on a live domain I confirmed it was working by running:
curl http://admin.workninja.com.com.au/auth -X POST
This gave me an expected error about credentials not being supplied but it shows that the server is actually receiving something. If you haven't realised the rails server when launched in production mode will respond to a POST request but still not a GET.
This is where my understanding of the problem breaks down. If
http://admin.workninja.local:3000/roles
works ("/roles being one of the applications routes") in a development environment why doesn't
http://admin.workninja.com.au/roles
work in a production environment as well? Can you assume from this fact that something isn't broken in the ruby codebase?
Below are some of the files relating to the configuration of the rails application in a production environment.
/config/deploy/production.rb
set :branch, 'master'
server 'ec2-54-66-230-174.ap-southeast-2.compute.amazonaws.com', user: 'ubuntu', roles: %w{app web db worker}
/config/environments/production.rb
Rails.application.configure do
# Settings specified here will take precedence over those in config/application.rb.
# Code is not reloaded between requests.
config.cache_classes = true
# Eager load code on boot. This eager loads most of Rails and
# your application in memory, allowing both threaded web servers
# and those relying on copy on write to perform better.
# Rake tasks automatically ignore this option for performance.
# This needs to be set to true in order for rails to launch in a production environment
config.eager_load = false
# Full error reports are disabled and caching is turned on.
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Enable Rack::Cache to put a simple HTTP cache in front of your application
# Add `rack-cache` to your Gemfile before enabling this.
# For large-scale production use, consider using a caching reverse proxy like
# NGINX, varnish or squid.
# config.action_dispatch.rack_cache = true
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
# Specifies the header that your server uses for sending files.
# config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
# Use the lowest log level to ensure availability of diagnostic information
# when problems arise.
config.log_level = :warn
# Prepend all log lines with the following tags.
# config.log_tags = [ :subdomain, :uuid ]
# Use a different logger for distributed setups.
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
# Use a different cache store in production.
# config.cache_store = :mem_cache_store
# Enable serving of images, stylesheets, and JavaScripts from an asset server.
# config.action_controller.asset_host = 'http://assets.example.com'
# Ignore bad email addresses and do not raise email delivery errors.
# Set this to true and configure the email server for immediate delivery to raise delivery errors.
# config.action_mailer.raise_delivery_errors = false
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation cannot be found).
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
# Use default logging formatter so that PID and timestamp are not suppressed.
config.log_formatter = ::Logger::Formatter.new
# Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false
# Application hostname
config.surgeforce.domain = 'surgeforce.com.au'
config.surgeforce.https = false
config.surgeforce.html5mode = true
end
/config/puma.rb
threads 1, 6
workers Integer(ENV['PUMA_WORKERS'] || 3)
on_worker_boot do
require "active_record"
cwd = File.dirname(__FILE__)+"/.."
ActiveRecord::Base.connection.disconnect! rescue ActiveRecord::ConnectionNotEstablished
ActiveRecord::Base.establish_connection(ENV["DATABASE_URL"] || YAML.load_file("#{cwd}/config/database.yml")[ENV["RAILS_ENV"]])
end
If you believe any other piece of the applications code to be critical in the investigation let me know and I'll include it.
The problem is that Rails' subdomain method is very simple and doesn't know anything about the structure of com.au domains. For "admin.workninja.com.au" which you use on production, the subdomain method would return "admin.workninja". From the docs:
Returns all the \subdomains as a string, so "dev.www" would be
returned for "dev.www.rubyonrails.org". You can specify a different tld_length, such as 2 to catch "www" instead of "www.rubyonrails" in "www.rubyonrails.co.uk".
And – without knowing your configuration– "admin.workninja" will very likely not match your config.workninja.admin_subdomain configuration anymore.
The solution is to configure a tld length of 2 on production. Just add the following to the configuration block in your config/environments/production.rb:
config.action_dispatch.tld_length = 2 # Defaults to 1

Override Radiant CMS's routes from the extension to set protocol to https

I am using a CMS called Radiant (version 0.9.1), Rails 2.3.18 and Ruby 1.8.7. I have to make the routes in this gem use the 'https'. I need to do it in such a way that I won't edit the gem source files itself, but rather override the gem's routes in the extension. How do I do this?
The configuration of the server really depends on what your server stack looks like
To configure your rails application to use SSL you need to force ssl
In your config/environments/production.rb:
config.force_ssl = true
To test ssl locally I would suggest trying thin as a webserver (also put config.force_ssl in development.rb to test this )
Add:
gem 'thin'
To your gemfile and start a thin ssl server:
$ thin start --ssl -p 3000
EDIT Rails 2 :
For Rails 2 this should work:
lib/force_ssl.rb
class ForceSSL
def initialize(app)
#app = app
end
def call(env)
if env['HTTPS'] == 'on' || env['HTTP_X_FORWARDED_PROTO'] == 'https'
#app.call(env)
else
req = Rack::Request.new(env)
[301, { "Location" => req.url.gsub(/^http:/, "https:") }, []]
end
end
end
config/production.rb
config.middleware.use "ForceSSL"
config/application.rb
require File.expand_path('../../lib/force_ssl.rb', __FILE__)
source: Force SSL using ssl_requirement in Rails 2 app

in a rails app, I have a route that does not work in https and gives a 404 error

I have a rails app. One of my routes only works in http and not in https. In https I get an 404 page not found error. The rest of the site works fine for both http and https. I'm not sure how to proceed in debugging/resolving this issue.
http://my.rails.app/mypage (works fine)
https://my.rails.app/mypage (404 page not found)
Simply use config.force_ssl = true in your environment configuration.
# config/application.rb
module MyApp
class Application < Rails::Application
config.force_ssl = true
end
end
You can also selectively enable https depending on the current Rails environment. For example, you might want to keep HTTPS turned off on development, and enable it on staging/production.
# config/application.rb
module MyApp
class Application < Rails::Application
config.force_ssl = false
end
end
# config/environments/production.rb
MyApp::Application.configure do
config.force_ssl = true
end

Adding an SSL Cert on heroku with godaddy

So here is the issue.
I have a site with SSL. (https://www.archerandreed.com/) It works great.
When you type archerandreed.com/ in the browser everything still works great.
Unfortunately when you type https://archerandreed.com/ or http://archerandreed.com/ into your browser you are given an SSL cert warning.
I thought I could just add a cert for www.archerandreed.com && archerandreed.com but heroku no longer accepts 2 ssl endpoints.
So what are the possible solutions. I assume one solution is to buy a wild card domain but that is painful. My app is rails 3.2.6. Is it possible to only force SSL if their is a subdomain? Can I do that in routes.rb or in config/environments/production.rb? Thanks for any help in advance.
So I think I found what I was looking for and I think this should be documented somehow in heroku...
1) turn off config.force_ssl = true:
config.force_ssl = false # config/environments/production.rb
2) in application_controller have the following:
class ApplicationController < ActionController::Base
before_filter :ensure_domain
before_filter :force_ssl
APP_DOMAIN = 'www.archerandreed.com'
protected
def force_ssl
if Rails.env.production?
redirect_to :protocol => 'https' unless request.ssl?
end
end
def ensure_domain
if Rails.env.production? && ((request.env['HTTP_HOST'] != APP_DOMAIN) )
# HTTP 301 is a "permanent" redirect
redirect_to( "https://#{APP_DOMAIN}", :status => 301) and return
end
end
end
Another solution (as brought up in a couple other threads - e.g. here) is to use a DNS provider like DNSimple that allows the use of ALIAS records. Then add an ALIAS record for your naked domain (in this case archerandreed.com), since you don't want to use A records for pointing your naked domain at heroku anyway.
You can then use config.force_ssl in production.rb and not have to add filters to your application controller.

Rails - Ensuring www is in the URL

I have my app hosted on Heroku, and have a cert for www.mysite.com
I'm trying to solve for
Ensuring www is in the URL, and that the URL is HTTPS
Here's what I have so far:
class ApplicationController < ActionController::Base
before_filter :check_uri
def check_uri
redirect_to request.protocol + "www." + request.host_with_port + request.request_uri if !/^www/.match(request.host) if Rails.env == 'production'
end
But this doesn't seem to being working. Any suggestions or maybe different approaches to solve for ensuring HTTPs and www. is in the URL?
Thanks
For the SSL, use rack-ssl.
# config/environments/production.rb
MyApp::Application.configure do
require 'rack/ssl'
config.middleware.use Rack::SSL
# the rest of the production config....
end
For the WWW, create a Rack middleware of your own.
# lib/rack/www.rb
class Rack::Www
def initialize(app)
#app = app
end
def call(env)
if env['SERVER_NAME'] =~ /^www\./
#app.call(env)
else
[ 307, { 'Location' => 'https://www.my-domain-name.com/' }, '' ]
end
end
end
# config/environments/production.rb
MyApp::Application.configure do
config.middleware.use Rack::Www
# the rest of the production config....
end
To test this in the browser, you can edit your /etc/hosts file on your local development computer
# /etc/hosts
# ...
127.0.0.1 my-domain-name.com
127.0.0.1 www.my-domain-name.com
run the application in production mode on your local development computer
$ RAILS_ENV=production rails s -p 80
and browse to http://my-domain-name.com/ and see what happens.
For the duration of the test, you may want to comment out the line redirecting you to the HTTPS site.
There may also be ways to test this with the standard unit-testing and integration-testing tools that many Rails projects use, such as Test::Unit and RSpec.
Pivotal Labs has some middleware called Refraction that is a mod_rewrite replacement, except it lives in your source code instead of your Apache config.
It may be a little overkill for what you need, but it handles this stuff pretty easily.
In Rails 3
#config/routes.rb
Example::Application.routes.draw do
redirect_proc = Proc.new { redirect { |params, request|
URI.parse(request.url).tap { |x| x.host = "www.example.net"; x.scheme = "https" }.to_s
} }
constraints(:host => "example.net") do
match "(*x)" => redirect_proc.call
end
constraints(:scheme => "http") do
match "(*x)" => redirect_proc.call
end
# ....
# .. more routes ..
# ....
end
I think the issue is you are running on Heroku. Check the Heroku documentation regarding Wildcard domains:
"If you'd like your app to respond to any subdomain under your custom domain name (as in *.yourdomain.com), you’ll need to use the wildcard domains add-on. ..."
$ heroku addons:add wildcard_domains
Also look at Redirecting Traffic to Specific Domain:
"If you have multiple domains, or your app has users that access it via its Heroku subdomain but you later switched to your own custom domain, you will probably want to get all users onto the same domain with a redirect in a before filter. Something like this will do the job:"
class ApplicationController
before_filter :ensure_domain
TheDomain = 'myapp.mydomain.com'
def ensure_domain
if request.env['HTTP_HOST'] != TheDomain
redirect_to TheDomain
end
end
end
Try this
def check_uri
if Rails.env == 'production' && request && (request.subdomains.first != "www" || request.protocol != 'https://')
redirect_to "https://www.mysite.com" + request.path, :status => 301 and return
end
end
Your best bet would be to set up redirect with your DNS provider, so it happens long before any request reaches your server. From the Heroku Dev Center:
Subdomain redirection results in a 301 permanent redirect to the specified subdomain for all requests to the naked domain so all current and future requests are properly routed and the full www hostname is displayed in the user’s location field.
DNSimple provides a convenient URL redirect seen here redirecting from
the heroku-sslendpoint.com naked domain to the
www.heroku-sslendpoint.com subdomain.
For proper configuration on Heroku the www subdomain should then be a
CNAME record reference to yourappname.herokuapp.com.
It's not just DNSimple that does this. My DNS provider is 123 Reg and they support it but call it web forwarding.

Resources