16

I'm using Devise for authentication in my Rails 3 app. The application uses PostgreSQL schemas and the Apartment gem to facilitate multi-tenancy.

Logging in and out of a specific subdomain is working great after an account is created. Users can only login on the subdomain for their specific account, which is great.

Here's where I'm running into issues...

A brand new user hits the sign up URL at:

http://foo.com/signup

By default, when they click submit, the new account is created, but the user is sent to:

http://foo.com/dashboard

Instead, I want them to go to:

http://myaccount.foo.com/dashboard

In order to achieve this, I overrode the after_sign_up_path_for method in my registrations_controller.rb file:

def after_sign_up_path_for(resource)
  root_url(:subdomain => resource.account.subdomain)
end

This works as intended--it loads the correct URL--but the user's session was created for the root domain (foo.com) instead of the subdomain, so the user is asked to sign in.

One suggestion I found is to change the config/initializers/session_store.rb to:

config.session_store :cookie_store, :key => '_domain_session', :domain => :all

But this allows anyone to login to an account on any subdomain, which obviously isn't cool.

Question: How can I ensure that the session created upon signup is valid for the subdomain that was created during the signup process

Rob Sobers
  • 20,737
  • 24
  • 82
  • 111
  • You need to change the cookie like you wrote in your question. otherwise the user will always be unknown on the subdomain. A simple solution would be to write a before-filter or extend your existing login-logic to check for the right subdomain. This is something, that is usually done, because you don't want to rely on cookies for authentication. – phoet Dec 13 '13 at 09:06
  • @phoet Hmm, I thought about that. So should I do `:domain => '.mydomain'` (to allow cross subdomain cookies) and then modify my `before_filter` to check the right subdomain? – Rob Sobers Dec 13 '13 at 13:18
  • i used a middleware for the cookie stuff: https://github.com/phoet/on_ruby/blob/master/app/middlewares/cookie_domain.rb – phoet Dec 13 '13 at 14:36
  • @phoet I'm not sure your middleware example will work for me because the cookie while the user is still on the root domain. Or does it re-cookie the user once they hit a subdomain? The thing I'm having the most trouble with is making Devise do an additional check on subdomain. Any advice there? I think if I can achieve this, I'll just enabled cross-subdomain cookies and rely on my auth code for security. – Rob Sobers Dec 13 '13 at 18:02
  • 1
    `before_filter` is good choice or just add a warden strategy for subdomain check which check for subdomain for valid check for user you can also use warden hook like `set_user` or `after_autentication` to achieve the same – Viren Dec 13 '13 at 19:17

2 Answers2

23

You could use domain: :all option in your config.session_store and just have a before_action just as suggested by some in the comments.

So you'll still have the code in config/initializers/session_store.rb or in config/application.rb:

config.session_store :cookie_store, :key => '_domain_session', :domain => :all

Then in your application_controller add the following code:

#app/controllers/application_controller.rb
before_action :check_subdomain

def check_subdomain
  unless request.subdomain == current_user.account.subdomain
    redirect_to root_path, alert: "You are not authorized to access that subdomain."
  end
end
Gjaldon
  • 5,534
  • 24
  • 32
  • 1
    This works, but one minor wrinkle: if I move sessions to the database (as `session_store.rb` suggests), when I create a brand new account (at http://example.com) I'm redirected to http://foo.example.com and I'm logged in (great!). However, the very next action I take forces me to login again. If I keep session in a cookie, the session persists as desired. Any idea what's going on there? – Rob Sobers Dec 15 '13 at 20:30
  • What version of Rails are you using? In Rails 4, activerecord-session_store has been removed. The only options available are :cookie_store, :mem_cache_store and :disabled. If your session data exceeds 4kb, then you'll want to use :mem_cache_store. Otherwise, it's best to stick with :cookie_store. It's not advisable to use your sql db for sessions since it is slower and is an unnecessary load on it. Here's an article on it: http://blog.remarkablelabs.com/2012/12/activerecord-sessionstore-gem-extraction-rails-4-countdown-to-2013 – Gjaldon Dec 16 '13 at 03:54
  • @RobSobers, let me know if answer has helped or if there's any clarification you need. – Gjaldon Dec 17 '13 at 01:50
  • I'm all set. Thanks! I ended up going with a slightly different solution for reasons I'll add to the question, but apart from that, what you've provided will work just fine. – Rob Sobers Dec 17 '13 at 03:04
  • @Gjaldon, I am trying to do the same thing without using the devise Can you please guide me what to do ?? – Mohd Anas Sep 18 '14 at 11:05
  • @Gjaldon I did exactly as u said but it is giving me error `undefined method `subdomain' for nil:NilClass` before_action works for every action can u help me with that please. after registration I want to bypass sign_in and url should be live pqrst.example.com/dashboard – thedudecodes Sep 06 '17 at 06:07
2

Override the devise session controller.

Create a file with the exact path app/controllers/devise/sessions_controller.rb

Override the sessions_controller class in that controller. Paste in the code found at the link. https://github.com/plataformatec/devise/blob/master/app/controllers/devise/sessions_controller.rb

class Devise::SessionsController < DeviseController
 # copy-paste the devise session controller below.
 ...
end

Edit the create action to suit your needs.

def create
  self.resource = warden.authenticate!(auth_options)
  set_flash_message(:notice, :signed_in) if is_flashing_format?
  sign_in(resource_name, resource)
  yield resource if block_given?
  respond_with resource, :location => after_sign_in_path_for(resource)
end

I'm looking to see if I can figure out how exactly to make this work, but I know for sure that the result you want is attainable by overriding the devise session controller.

EDIT

If you are using cross-subdomain cookies, you could enforce the subdomain session with a before_filter. For example

before_action do 
    redirect_to root_path, alert: 'That subdomain does not belong to you' if request.subdomain != current_user.subdomain
end
OneChillDude
  • 7,856
  • 10
  • 40
  • 79
  • where to write this before_action as writing it in application controller will call it everytime when anymethod is called and will give error – thedudecodes Sep 06 '17 at 06:26