Rails 4 multitencancy with subdomain login management - ruby-on-rails

Scenario: Multi-tenant rails app that uses subdomains and devise
Problem: I want the user to be able to log into mydomain.com then be forwarded to their own subdomain1.mydomain.com address as a logged-in user. Right now they can only log directly into their own subdomain.
I'm a relative Rails newbie and I can't find a simple solution (although it seems like there must be one). Ideally I would like to have mydomain.com and subdomain1.mydomain.com share one cookie, but my skills aren't there for writing custom middleware. Obviously since it's multitenant I can't share one session across all subdomains. Stuck on this for a few days and curious if there is a simple solution (such as a config.session_store domain setting) that I'm missing before I start looking at OAuth or other more cumbersome solutions. Any help will be appreciated!
Edit: Of course I only found this after posting. Log a user into their subdomain after registration with Rails and Devise . Will try the config.session_store domain: :all with a before filter recommendation and post any details if it doesn't work, seems like a good idea at least.
Edit: SOLUTION that worked for my particular Devise with subdomains setup:
class ApplicationController < ActionController::Base
before_action :check_subdomain
def check_subdomain
unless request.subdomain == "" or request.subdomain == session[:subdomain]
redirect_to request.protocol+request.domain
end
end
end
session_store.rb
My::Application.config.session_store :cookie_store, key: '_my_session' , :domain => :all, :tld_length => 2
Basically I set the subdomain in the session with session[:subdomain] at login and use that to scope the session to the current user. Otherwise when the domain is set to :all in session_store it breaks the scope. If the user is not authorized it redirects them to the public home page via the request.protocol (http:// or https://) +request.domain redirect. Simple! Now users can move between the base domain and their subdomain within the same session.

Cookie
From what you've posted, I'd estimate you have a problem with the tracking of your session cookie. We had a similar problem with our subdomain-powered application, which lead to the cookie being dropped each time you switched between the two
We found the remedy here:
Share session (cookies) between subdomains in Rails?
#config/initializers/session_store.rb
Your_App::Application.config.session_store :cookie_store, key: '_your_app_session', domain: :all, tld_length: 2
The trick is the tld_length argument - this allows you to define how many "levels" of the domain can be accommodated; IE if you're using a sub domain, you'll need to set the tld_length to reflect it
Forwarding
I'm not sure whether you have a problem with your forwarding or not; I'll give you some ideas anyway.
When you log into a "subdomain", unless you've got a true multi-tenancy implementation of Rails (where each user is stored in a different database), you should be able to allow the users to login on the main form, and then redirect them to the subdomain without an issue
Something you need to consider is the subdomain constraint will only be populated if you use _url path helpers:
<%= link_to "Your Name", path_url(subdomain: "subdomain_1") %>
The reason for this is the _path helper is relative to the base URL, and consequently cannot populate the subdomain option. Alternatively, the _url path helper points to the URL in its entirety -- allowing you to define the sub domain as required
--
If you send the request & continue to want the user to remain signed-in, you'll need to ensure you're able to persist the authentication across the sub-domains. IE if you have a single-sign in form on the "main" page, you'll want to ensure you can continue the authentication into the subdomains

Related

Change Rails session cookie domain without logging users out

I'm using Rails 4.2.2 (with Devise 3.4.1) and am changing the cookie_store domain from www.boundless.dev to .boundless.dev in order to share the same session across all of our subdomains (single sign-on).
Boundless::Application.config.session_store :cookie_store, key: '_boundless_session', domain: '.boundless.dev'
If I make this change alone. Existing logged-in users who return to the site will end up with 2 _boundless_session cookies, one with domain boundless.dev and the other with www.boundless.dev. Somehow this makes logging out impossible.
Is it possible to make this change without logging all users out of the site?
I thought that I'd be able to write a method as a before_filter in my ApplicationController to delete the session cookie and replace it with a new one at .boundless.dev, but it doesn't work, and I suspect it has something to do with the remember_user_token cookie.
def update_session_cookie_domain
session_cookie = cookies['_boundless_session']
cookies.delete('_boundless_session', domain: 'www.boundless.dev')
cookies['_boundless_session'] = {
value: session_cookie,
domain: '.boundless.dev'
}
end
I was able to solve this problem by changing the cookie name used for the session.
So the original config was:
Boundless::Application.config.session_store :cookie_store, key: '_boundless_session', domain: 'www.boundless.dev'
And I changed it to:
Boundless::Application.config.session_store :cookie_store, key: '_boundless_session_NEW', domain: '.boundless.dev'
I expected this to log users out, but it doesn't for some reason that I don't quite understand.
Unfortunately, I've yet to find a way to clear the old _boundless_session cookie, but at least now I can log out after having my session cookie updated to the more general domain.

Session across domains in Rails 4

I have an issue with wanting to use session across domains (not subdomain). Eg, I have .co.uk, .com.au, and .com all for the same address.
I know for subdomains I can use something like:
SomeApp::Application.config.session_store :cookie_store, key: '_some_app_session', domain => :all, :tld_length => 2
But I would like my solution to work between actually domains to have one set of sessions/cookies.
As your default session store is 'cookie_store'
You could just do it the same way as when you might send an email link with an authentication token. Check to verify that the cookie is correct on example.org and, if it is, redirect them to:
http://example.com?token=
and then check to make sure the token matches the one you have in the DB when they arrive. If the token does match, create the session cookie for the example.com domain and then change the token in the database.
This will successfully transfer from one domain to another while providing persistent login on the new domain (via cookie) and shutting the door behind them by changing the authentication token in the DB.
EDIT
To answer your question below, I don't think you need middleware or anything fancy. You could do a simple before filter in the application controller of example.org, something like:
before_filter :redirect_to_dot_com
...
def redirect_to_dot_com
url = "http://example.com" + request.fullpath
url= destination + (url.include?('?') ? '&' : '?') + "token=#{current_user.token}" if signed_in?
redirect_to url, status: 301
end
That will redirect the user either way, and append the token to the query if the user is signed in on the .org site.
Go to more details on Persisting user sessions when switching to a new domain name (Ruby on Rails)
I wouldn't use the PHP style routings which pass ?php=bad style variables via :get especially if you're already using sessions. And also since then you'd have to parse the original URL and a bunch of other work.
Instead of using session[:edition_id] = 'UK' you can use:
cookies[:edition_id] = { value: 'UK', domain: 'some-app.com', expires: 1.year.from_now }
# or if you want to be google 10.years.from_now
When you use session[:edition_id] = 'UK' the value will be encrypted by rails and stored in the _myapp_session cookie. But in your case that probably doesn't matter much.
If you set the cookie explicitly on the domain you want to read it from, it will work without having to set odd ball variables via get and then trying to interpret them again on redirect.

Using Devise to auto login a user on a multi tenancy site with subdomains

In my app a customer registers on mysite.com. Once registration is complete, they are given a site such as customer.mysite.com. I'm using Devise and would like to log the customer into their site immediately. I'm using multitenancy as explained in the RailsCast here. I'm not quite sure how to go about this. The standard solution of adding sign_in to an after_sign_up_path_for def isn't working. I'm assuming it's trying to log the customer into mysite.com, not customer.mysite.com. I'm including my after_sign_up_path_for def so you can see what I'm trying with no success. The resource in my Devise implementation is User and a user has a Site.
RegistrationsController:
def after_sign_up_path_for(resource)
# Site.current_id = resource.site.id
sign_in resource, bypass: true
"http://#{resource.site.host}.#{ENV['BASE_HOST']}/sites/#{resource.site.id}/edit"
# edit_site_url
end
Any help is appreciated.
I had the same issue and solved it the following way:
Create a new model (I called it LoginKey) that contains the user_id and a random SHA1 key.
When the user is authenticated at the parent domain (for example: mydomain.com/users/sign_in), a new LoginKey is created and the user is redirected to the corresponding subdomain to an action that I called login_with_key (for example: user_subdomain.mydomain.com/users/login_with_key?key=f6bb001ca50709efb22ba9b897d928086cb5d755322a3278f69be4d4daf54bbb)
Automatically log the user in with the key provided:
key = LoginKey.find_by_login_key(params[:key])
sign_in(key.user) unless key.nil?
Destroy the key:
key.destroy
I didn't like this solution 100%, I tried out a lot of different approaches that do not require a db record to be created, but always faced security concerns, and I think this one is safe.

Path specific cookies in Rails app

I'm working on a minisite generator managed in one app.
Each minisite would have its own authentication.
With subdomains, I'm able to be specifically logged in and have settings for only one minisite but not another.
Now, I would like to have a failover where each minisite is accessible without setting up subdomains : http://mygenerator/minisites/1123
Is it possible for cookies to be scoped at the minisite/ path level ?
Or, is there a way to dynamically tweak the cookie key in a before_filter at the controller level ?
I took a look at the :path option in session_store config, but I don't think it's relevant, and it screws up Devise in a redirection loop.
Thanks !
I know its a late reply but i havent found anything in the internet answering this question.
My problem was that the application always running on a subpath but the session cookie was always issued to /
For me this was fixed by setting and using Rails.application.config.x.base_url
for your specific problem you can figure the path to set from the request object given in the method
This needs to be added in a new file in your rails Applcation ./config/initializers/session_store.rb
module SessionPath
extend ActiveSupport::Concern
def self.included(base)
base.class_eval do
alias_method :set_cookie_original, :set_cookie
alias_method :set_cookie, :set_cookie_extended
end
end
def set_cookie_extended(request, session_id, cookie)
cookie[:path] = Rails.application.config.x.base_url #or what you need
set_cookie_original(request, session_id, cookie)
end
end
ActionDispatch::Session::AbstractStore.send(:include, SessionPath)

Is there a way to specify subdomains that a cookie should be saved to? (rather than one, or all)

I got my session variables being saved to all subdomains on my site via this:
Site::Application.config.session_store(
:cookie_store,
:key => '_site_session',
:domain => Settings.domain,
# :secure => (Rails.env.production? || Rails.env.staging?),
:http_only => true)
where Settings.domain = '.site.com'
However, the way my site is setup, each account has their own subdomain. So this caused a big error since when someone logged in, they could enter any other subdomain and have access to that account (not all information, but some..weird).
So, what I want to do is allow people to share session variables with public subdomains (signin.site.com and signup.site.com) as well as their own personal subdomain (account1.site.com). However, those are the only ones that should be able to share it. Is there a way to specify that?
I'm assuming you handle logins on a subdomain like signin.site.com. It's not allowed to set the cookie for *.site.com then.
Handling the authentication and setting the cookie from http://site.com should solve the problem.

Resources