We have been working on an application that allows administrators to create accounts for their users. Each account will be accessible under a subdomain, so we needed to setup a subdomain environment inside our application, and also in our development machine. In addition, we must be able to let the users signed in among several subdomains, as a user can access other accounts when allowed.

After some research we decided to go with the subdomain-fu gem, which is great to give your application the ability of handling subdomains. Another great resource we have used is Ryan Bates’ screencast about the subject. But they did not solve our problem completely, so here we are going to document a few steps to help you get up and running easily with subdomains and sessions.

Setup a development environment

As we need to test all the subdomain stuff in our application while developing it, we are going to need some extra setup for our development environment. However, our local machine knows nothing about subdomains, so how do we do that? We need to tell our machine which subdomains we will be using, manually. You can do this by editing your hosts file, located at /etc/hosts, and configuring the subdomains you are goint to need:

We are making explicit all domains and subdomains that will point to our local machine, under IP 127.0.0.1. By default localhost was already there, so we have just added the needed subdomains. When working with this subdomains I personally like to create a domain like my_app.local, and point subdomains to it like sub.my_app.local. There are some other ways to create this setup, but we would rather go with this due to readability and easy configuration.

To make sure everything is fine we need to clear our dns cache:

Under linux:

/etc/init.d/dns-clean start

Under OS X:

dscacheutil -flushcache

And that is it. Restart your application and try it out. Access it under the new domain or subdomain you have created, for instance local.host:3000, to see if everything is working fine.

You may be asking why we could not be using just subdomain.localhost instead of subdomain.local.host as subdomain configuration. And you are right, we could use it, and it should work fine to access subdomains. However, there are some “bumps in the road” while sharing sessions, so keep reading!

This config file can be placed at config/initializers folder. Basically it tells subdomain-fu the sizes of the domain for each environment, i.e. 0 for localhost, 1 for foo.com, 2 for foo.bar.com.

The gem gives you some special powers through the current_subdomain method in your controllers, and also some adds to your routes like using the :subdomain option: root_url(:subdomain => 'foo') #=> foo.local.host.

Routing

Chances are you are going to need specific actions to be handled only under subdomains, but not in the root domain or a specific subdomain, like admin.foo.com. And subdomain-fu will help you here: you just need to setup your routes using the :subdomain condition, like the example below:

Finally, everything is perfect, you are now able to test subdomains in your environment, create some filters to ensure the subdomain exists and your user has access to it, sessions work as expected, and so on, right? Nope. We are not ready yet. Sessions are our pain.

Sharing sessions between subdomains

Due to security issues, browsers do not allow sharing cookies using only .com, just under the complete domain like foo.com, and of course it is totally right. Could you imagine sharing sessions between gmail.com and hotmail.com, just because they are both .com? I could not. The same rule applies while trying to share sessions using localhost only. Browsers will not allow you sharing sessions between subdomains under localhost, and some weird issues may appear. We have had AuthenticityToken errors while trying to use localhost.

We are going to use this setup now. To enable sharing sessions in your application you need to configure the :domain option in your session config hash, for each environment. Here is how to do it in development config:

config.action_controller.session = { :domain => '.local.host' }

Please note the dot prepended to the domain. It will enable sharing sessions between all subdomains in your application. Think about it as *.local.host, including local.host itself. By doing this, you are now able to sign a user in one subdomain, or even the root domain, and redirect it to any other subdomain, for instance. The session will be kept and the user will stay signed in as expected.

Do not forget to setup the production environment with the same config, pointing to the real domain of your application.

Testing

We are using cucumber in this application together with celerity/culerity, and at the beginning it was kind of pain to get it up and running. The first thing you must bear in mind is that you always have to setup the host you are testing. By default, cucumber uses host example.com. And that is okay for default Rails integration tests, except when we use “real browsers” tests like celerity or selenium. You have to set it up by yourself. Just create two steps like this:

Given /^I am visiting the root application$/ do
host! 'local.host'
end
Given /^I am visiting the subdomain "([^\"]*)"$/ do |subdomain|
host! "#{subdomain}.local.host"
end

And use then inside your features:

Given I am visiting the subdomain "my_sub"

That should do the trick. Make sure you use only subdomains you have configured in your hosts file, or you will get some weird errors =).

Mailers

We had some issues while creating mailers with links pointing to the subdomain. As our users has many subdomains, we don’t know where we should point the user inside the mailer, due to default lack of request context as we have in the controller. To solve this we could not use any class accessor, because they are not thread safe. So we decided to go with Thread.current. Just create a filter in your application controller:

Then create a new helper called MailHelper inside your app/helpers folder, adding a method to obtain the subdomain:

def current_subdomain
Thread.current[:current_subdomain]
end

This MailHelper is a module provided by Rails which is included in all mailers. Now you are able to create links inside your mailers using the current_subdomain method, just like you do in your controllers:

What about Devise?

Devise has been doing a great work while authenticating a user under a specific subdomain. There are two cases to be handled: the first one is when your User model has a subdomain attribute and you want the authentication process take into account this subdomain with the current_subdomain. First of all you need to update devise call inside your user model to add the :authentication_keys option:

devise :all, :authentication_keys => [:email, :subdomain]

This will tell devise to find the user based on both subdomain and email. Then you have to add the subdomain to your sign in form as a hidden field, so Devise will be able to get this information easily from params while authenticating the user:

f.hidden_field :subdomain, :value => current_subdomain

The second case happens when your subdomain data is inside another model associatied with the user, let’s say a user has many accesses. In addition to what we have done in the first case, we must override a class method from Devise to add our own condition for finding the user:

Devise is now totally capable of handling authentication based on subdomains. Remember: Devise is managing authentication, so it will not be able to do anything after the user signs in. Be sure to also add filters to your controllers to ensure a user will never access a subdomain it has no access.

Here we go!

A few steps are needed to get our development machine up and running to create an application using subdomains, but they are key steps to ensure you are not going to have problems while starting.

What about you? Have you ever developed an application using subdomains? Have you run through any of these issues, or maybe another you want to share? Do you have any tip?

@tomislav I think applications won’t require that much subdomains to test while in development. Actually I’m using just 3, and one of them is also configured in our test environment so tests always run in a “real” configured subdomain. Due to it we decided to go manually edit /etc/hosts.

In addition, instead of editing your /etc/hosts file each time you need a new subdomain, you could also create a PAC file and setup your browser to use it as a proxy. There is an example of a file like this in the railscast I’ve linked in the text. That should do the trick.
I hope that helps =).
Thanks, Carlos.

@tomislav I think applications won’t require that much subdomains to test while in development. Actually I’m using just 3, and one of them is also configured in our test environment so tests always run in a “real” configured subdomain. Due to it we decided to go manually edit /etc/hosts.

In addition, instead of editing your /etc/hosts file each time you need a new subdomain, you could also create a PAC file and setup your browser to use it as a proxy. There is an example of a file like this in the railscast I’ve linked in the text. That should do the trick.
I hope that helps =).
Thanks, Carlos.

George Guimarães

@tomislav

You can use DNS to do that. Instead of local.host that Carlos used, you can use a real DNS domain, like local.infinum.hr.

It’s possible to use a wildcard to map all *.local.infinum.hr to 127.0.0.1. So, there would be no reason to dinamically mess with /etc/hosts. Sure… this may be a bit overwhelming but it’s a simple DNS-hack that works.

Here at Plataforma we have never used such config, but I have personally used this when building/testing some personal projects.

Another aproach would be configuring an local DNS resolver, but that’s even more cumbersome.. =D

George Guimarães

@tomislav

You can use DNS to do that. Instead of local.host that Carlos used, you can use a real DNS domain, like local.infinum.hr.

It’s possible to use a wildcard to map all *.local.infinum.hr to 127.0.0.1. So, there would be no reason to dinamically mess with /etc/hosts. Sure… this may be a bit overwhelming but it’s a simple DNS-hack that works.

Here at Plataforma we have never used such config, but I have personally used this when building/testing some personal projects.

Another aproach would be configuring an local DNS resolver, but that’s even more cumbersome.. =D

I wonder if you looked at Matthew Hollingworth’s subdomain_routes gem as an alternative to the subdomain-fu gem. He says it’s useful in URL generation, route recognition, and better for route definition. The subdomain_routes code for the routes.rb file looks simpler than what’s needed for subdomain-fu.

I’m about to start a project which requires subdomains (and uses Devise), so I’m curious if you made a choice (and why) between subdomain_routes and subdomain-fu.

I wonder if you looked at Matthew Hollingworth’s subdomain_routes gem as an alternative to the subdomain-fu gem. He says it’s useful in URL generation, route recognition, and better for route definition. The subdomain_routes code for the routes.rb file looks simpler than what’s needed for subdomain-fu.

I’m about to start a project which requires subdomains (and uses Devise), so I’m curious if you made a choice (and why) between subdomain_routes and subdomain-fu.

@Daniel I haven’t looked at subdomain_routes, but it seems to be a nice library for handling subdomains. I’ll definitely take a closer look at this library.

Anyway, subdomain_routes should do the trick as well as subdomain-fu did. You shouldn’t have issues with both. The main difference I can see for your code is the way you’ll be using routes, as subdomain full requires you to use :subdomain key instead of adding it to the named route. And you just need to test whether the model-based subdomains will work as expected for you specific case.
I’d encourage you to give it a try with subdomain_routes. Also, you could provide some feedback here again if you decide so.

@Daniel I haven’t looked at subdomain_routes, but it seems to be a nice library for handling subdomains. I’ll definitely take a closer look at this library.

Anyway, subdomain_routes should do the trick as well as subdomain-fu did. You shouldn’t have issues with both. The main difference I can see for your code is the way you’ll be using routes, as subdomain full requires you to use :subdomain key instead of adding it to the named route. And you just need to test whether the model-based subdomains will work as expected for you specific case.
I’d encourage you to give it a try with subdomain_routes. Also, you could provide some feedback here again if you decide so.

Really good post, as always. I also use Ghost for my local machine too.

In case of mailer I normally store the subdomain with another account data in database table, so when a rake taks or cron call a mailer I allways query for account and pass that account as body param to mailer. But your aproach with Threads are great too, thanks.

For wildcard subdomains is really easy to setup like George said, but you should remember if you release it on web you will need a wildcard ssl certiface and it will cost to you arround $200 year.

Really good post, as always. I also use Ghost for my local machine too.

In case of mailer I normally store the subdomain with another account data in database table, so when a rake taks or cron call a mailer I allways query for account and pass that account as body param to mailer. But your aproach with Threads are great too, thanks.

For wildcard subdomains is really easy to setup like George said, but you should remember if you release it on web you will need a wildcard ssl certiface and it will cost to you arround $200 year.

You’ll want to make http://www.subdomain.domain act the same as subdomain.domain, especially if you’re writing an application that will be used by the general public. A very large percent of the population think everything on the web has to start with “www.” even if you tell them otherwise. The press will do the same thing, no matter how much effort you put into giving them the correct URL.

So, save yourself from a lot of headache, and your users from a lot of confusion, by just making the two equivalent in your application.

You’ll want to make http://www.subdomain.domain act the same as subdomain.domain, especially if you’re writing an application that will be used by the general public. A very large percent of the population think everything on the web has to start with “www.” even if you tell them otherwise. The press will do the same thing, no matter how much effort you put into giving them the correct URL.

So, save yourself from a lot of headache, and your users from a lot of confusion, by just making the two equivalent in your application.