Rails - Ensuring www is in the URL - ruby-on-rails

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.

Related

On Heroku, why is www causing a 'redirected you too many times' error while random subdomains work correctly?

I've just pushed a new app to a Heroku staging environment for the first time, and am getting a strange redirected you too many times error in the browser. How should I go about debugging this, and determining whether this is an issue in the app or an issue with the DNS records.
Some background - this is a multitenancy app, with subdomains for different users/ accounts. The app checks if the subdomain is recognized, and if not redirects to the root. As I had trouble configuring Heroku and my DNS provider for a root domain, I am using 'www'.
application_controller.rb
helper_method :subdomain, :current_account
before_filter :validate_subdomain
def current_account
#current_account ||= Account.where(subdomain: subdomain.to_s ).first
end
def subdomain
request.subdomain
end
def has_unrecognized_subdomain
subdomain.present? && %w(www admin api).exclude?( subdomain ) && current_account.nil?
end
def validate_subdomain
if has_unrecognized_subdomain
redirect_to root_url( :subdomain => 'www' )
end
end
I've added the root domain and subdomains to Heroku
heroku domains:add example.com
heroku domains:add *.example.com
and configured my DNS records
* CNAME myapp.herokuapp.com
My logs were showing the following
Filter chain halted as :validate_subdomain rendered or redirected
and so I disabled the redirect_to part of validate_subdomain to investidate further.
Now, if I include a random subdomain in the URL, the page is rendered correctly in the browser. But if I include 'www' I get a browser error. In Chrome:
The page isn’t working
www.example.com redirected you too many times.
ERR_TOO_MANY_REDIRECTS
Nothing is added to my logs when visiting a 'www' url, whereas logs are added with any other random subdomain string.
I would be very grateful for a sanity check - does this sound like a DNS issue rather than an app issue? And if so, are the CNAME records where I should be looking?

Can I make Rails / WEBrick recognize entries in /etc/hosts as subdomains (instead of domains)?

I'm trying to use subdomains locally for a Rails app so I added the following line to my /etc/hosts file:
# add 'test' subdomain for localhost
127.0.0.1 test.localhost
Now I can point my browser to test.localhost:3000 and it hits my Rails app.
However, Rails or WEBrick interprets the whole darn thang as the domain:
# logging in the controller
logger.debug("domain: '#{request.domain}', subdomain: '#{request.subdomain}'")
# output in the console
domain: 'test.localhost', subdomain: ''
Is there an easy way to get WEBrick Rails to interpret test as the subdomain?
Thanks!
Update
I ended up making a before_action as a workaround.
def set_domain_and_subdomain
#domain = request.domain
#subdomain = request.subdomain
# HACK: force /etc/hosts subdomains
if Rails.env.development?
if m = request.domain.match(/([^\.]+).localhost/)
#subdomain = m[1]
#domain = 'localhost'
end
end
end
But I'm still curious if there's a way to do this universally on my computer (i.e. in `/etc/hosts or something)
Pretty late to find this post, but for posterity: https://github.com/rails/rails/issues/12438
Setting the top level domain length (TLD) allowed request.subdomain to target the subdomain as you'd expect.
I put config.action_dispatch.tld_length = 0 into config/environments/development.rb and everything worked swimmingly.
Remember to restart your server

Subdomains on subdomain host route in Rails 3 -- hits route, not root

Currently I have myapp.herokuapp.com as staging for a little project I'm working on. Once finished it'll be running under myapp.com.
When I'm running it locally, I'm using lvh.me:3000 to test things. This is working well for the problem I'm having, but since this staging environment is under a subdomain I'm having routing issues.
The problem is that my users are given a custom subdomain, thus, someuser.myapp.com points to users#landing. I accomplish this locally with this route:
match '', to: 'users#landing', constraints: lambda { |r| r.subdomain.present? && r.subdomain != 'www' }
My issue is that when I visit myapp.herokuapp.com I end up hitting users#landing instead of my root.
I'm seriously drawing a blank.
Totally overlooked what I was doing...
I ended up,
if request.subdomain == 'myapp'
redirect_to root_url
else
Which fixed the problem. I'm sure there's a better way to do this though.
Edit: furthermore, I added default_url_options in application_controller.rb
def default_url_options
if Rails.env.production?
{:host => 'myapp.herokuapp.com'}
else
{:host => 'lvh.me'}
end
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.

how to setup rails Authenticity Token to work with multiple domains?

I'm building an app that uses subdomains as account handles (myaccount.domain.com) and I have my sessions configured to work across the sub-domains like so:
config.action_controller.session = {:domain => '.domain.com'}
In addition to the subdomain a user can input a real domain name when they are creating their account. My Nginx config is setup to watch for *.com *.net etc, and this is working to serve out the pages.
The problem comes when a site visitor submits a comment form on a custom domain that was input by the user. The code is throwing an "Invalid AuthenticityToken" exception. I'm 99% sure this is because the domain the user is on isn't specified as the domain in the config.action_controller.session. Thus the authenticity token isn't getting matched up because Rails can't find their session.
So, the question is: Can you set config.action_controller.session to more than 1 domain, and if so can you add / remove from that value at runtime without restarting the app?
I found the answer to this question here: http://codetunes.com/2009/04/17/dynamic-cookie-domains-with-racks-middleware/
This solution worked for me because my app was running on Rails 2.3.5, which uses Rack. The request comes from web server, goes through middleware layers and enters the application. So this middleware layer detects the host with which the application is accessed and sets cookie domain for the request. Here it is:
# app/middlewares/set_cookie_domain.rb
class SetCookieDomain
def initialize(app, default_domain)
#app = app
#default_domain = default_domain
end
def call(env)
host = env["HTTP_HOST"].split(':').first
env["rack.session.options"][:domain] = custom_domain?(host) ? ".#{host}" : "#{#default_domain}"
#app.call(env)
end
def custom_domain?(host)
domain = #default_domain.sub(/^\./, '')
host !~ Regexp.new("#{domain}$", Regexp::IGNORECASE)
end
end
# turn it on in environment.rb
config.load_paths += %W( #{RAILS_ROOT}/app/middlewares )
# production.rb
config.middleware.use "SetCookieDomain", ".example.org"
.example.org is the default domain that will be used unless the application is accessed via custom domain (like site.com), we give it different values depending on environment (production/staging/development etc).
# tests/integration/set_cookie_domain_test.rb (using Shoulda and Webrat)
require 'test_helper'
class SetCookieDomainTest < ActionController::IntegrationTest
context "when accessing site at example.org" do
setup do
host! 'example.org'
visit '/'
end
should "set cookie_domain to .example.org" do
assert_equal '.example.org', #integration_session.controller.request.session_options[:domain]
end
end
context "when accessing site at site.com" do
setup do
host! 'site.com'
visit '/'
end
should "set cookie_domain to .site.com" do
assert_equal '.site.com', #integration_session.controller.request.session_options[:domain]
end
end
context "when accessing site at site.example.org" do
setup do
host! 'site.example.org'
visit '/'
end
should "set cookie_domain to .example.org" do
assert_equal '.example.org', #integration_session.controller.request.session_options[:domain]
end
end
end

Resources