Are browser sessions unique for each subdomain? - ruby-on-rails

Say I'm running a multi-tenant application that gives each organization its own portal via a subdomain.
Example -
orgA.application.com
orgB.application.com
etc...
Each subdomain reads from a different schema/tenant in my PSQL db, but is otherwise the same application.
In my ApplicationController I set the current_user as -
def current_user
if session[:user_id]
#current_user ||= User.find_by_id(session[:user_id])
end
end
There are few admin/superusers such as myself that have a user account on each subdomain. If I log into orgA with my user (id = 22), then my session gets set as user_id: 22.
Now say I want to switch over to orgB, where my user id is 44. If I log into orgB after having set my session in orgA, is there any chance I could accidentally log myself in as the user who is 22 on orgB?
More fundamentally, I'm trying to understand how a browser cookie session is set. From my understanding, it's a hash of variables that are encrypted and cached in the client's browser. Is that set per subdomain? Or do all subdomains of a particular site share the same cache/session cookie?
More importantly, how do I prevent cross pollination of sessions like in the example above? Is my current_user method too basic?
Thanks!

You're fundamentally asking about cookies here, to which the answer is relatively simple: cookies are not shared across subdomains unless you explicitly request it.
When you send the Set-Cookie HTTP header to create a cookie in the user's browser, you can choose whether or not to include a domain configuration option. That option controls which domain the cookie saves under and will be served to.
By default, if you send Set-Cookie with no domain option, the cookie will be set for the current hostname, which includes subdomains. That is, a cookie set on siteA.example.com will not be accessible to siteB.example.com.
If you send a domain option of example.com when you create your cookie on siteA.example.com, then the cookie will be accessible on both example.com and *.example.com, so all your sites will be able to access it.
For your situation, then, you should send the Set-Cookie header with no domain option. That's the default in most setups, including Rails so it's unlikely you need to do anything.

Related

Customize Devise Cookie

I'm using devise in my rails application. I have it configured in a tenanted manor in which accounts/sessions are scoped to a subdomain. For example:
http://subdomain1.example.com/
http://subdomain2.example.com/
...
This works well but I want to have an additional subdomain for "super-admins" that allows those users navigate to all the other subdomains without the need to reauthenticate. This would be something like:
http://admin.example.com/
Is it possible to customize the cookie that gets generated on only the admin subdomain so that it is valid on all other subdomains?
Cookie domains are more inclusive the less specifically they are defined, so if you have a controller that only serves the admin subdomain, you should be able to set a cookie for the .example.com (or example.com) domain and expect it to be available to all other subdomains.
These docs describe the process for selectively setting the cookie domain.

Persisting user sessions when switching to a new domain name (Ruby on Rails)

My Rails app is currently available at example.org, but I want to switch to example.com
Doing a wildcard 301 redirect in routes.rb isn't a problem, but I would like to persist the user sessions as well. Since the new domain won't have access to the cookies of the old domain, what's the best (secure and as easy as possible) way to redirect the user to the new domain and still have him/her signed in?
I've found numerous of threads talking about setting up cross-domain web apps using complicated authentication tokens methods, but I'm looking for a one-time one-way migration so I'm hoping the solution will be simpler for this.
Any suggestions?
I'm using Ruby on Rails 3, OmniAuth, and using the default 'cookie_store' as my session 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=<their 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.

Redirecting Rails non www to www without disrupting user sessions?

Ok, so I want to redirect all non www domain requests to www.mydomain.com simply for Google/search engine consistency (I know all about canonical tags/google webmaster tools, let's ignore that)
I know the standard way to do this through Nginx (or htaccess for Apache).
However, the thing is this will probably disrupt user sessions right? For instance, if someone logged in through domain.com, their cookies will only be valid for domain.com, NOT www.domain.com, so by redirecting I'd make all users have to log in again.
Is there a way to make the redirect w/o disrupting user sessions?
Perhaps I can do something where I check for cookies? If it doesn't exist, then do the redirect. If it does, then don't redirect.
def index #controller method for requests from domain.com or www.domain.com
#user = #requestManager.authorizedUser(cookies, params, logger) //check for cookies
#if no cookies (Google bot or new visitor)
#and request is from domain.com, redirect to www.domain.com
if(#user == nil and request.host =~ /^domain.com/)
logger.info("Redirecting")
redirect_to "http://www.domain.com", :status => 301
return
end
end
Would this work in redirecting non www to www.domain.com, and not disrupting current user sessions?
If a cookie's domain hasn't been explicitly set it will be restricted to the hostname of the site where it was set.
If your users have long-term sessions that you really value, then this might achieve your goal:
modify your site to set domains wtih all cookies.
use firebug to check that the cookie is re-issued with the new domain when a user visits the site
wait a while until at least most of your users have visited the sit
enable the redirection
The users whose sessions will be lost are users who don't visit the site during the waiting period.

can I have my domain authenticated for regualr domain and www.mydomain?

How can I have user automatically logs in to my website if he goes to mydomain.com or www.mydomain.com?
The problem is that cookies set on the main domain are not automatically accessible on the subdomain. Authentication in your application is handled through sessions, which are persisted between requests by a cookie. That the cookie should be accessible to your subdomains has to be set explicitly when the cookie is related. This question dealt with how:
Problem with sessions, subdomains and authlogic in Rails

Specify Cookie Domain in Authlogic When Session Is Created

Is it possible to set the cookie domain to something other than the current domain when a session is created with Authlogic?
When a new account is created from our signup domain, I'd like to redirect the user to their subdomain account and log the user in.
Current controller:
def create
#account = Account.new(params[:account])
if #account.save
#user_session = #account.user_sessions.create(#account.users.first)
# I'd like the cookie domain to be [#account.subdomain, APP_CONFIG[:domain]].join(".")
redirect_to admin_root_url(:host => [#account.subdomain, APP_CONFIG[:domain]].join("."))
else
render 'new'
end
end
If you do:
config.action_controller.session[:domain] = '.YOURDOMAIN.COM'
in your production.rb file, that will allow you to have everyone logged in on all subdomains of your subdomain. If you then add a filter (or whatever, but I use a filter so I know that works) that checks that someone is actually using the right domain before you show controller stuff, it works pretty well.
As an example, you could store the appropriate subdomain for the session as a session variable and give people link options to their specific things if they were on your main domain or looking at a page on someone else's subdomain.
This seems to be the general pattern for doing this sort of thing -- if you set a cookie specific to the subdomain otherwise you won't be able to tell when they've logged in to the main site. I also have a 'users_domain?' helper that ends up getting called occasionally in views when I do this.
If you don't want to have those sorts of common web design patterns, wesgarrion's single use -> session creation on subdomain is also a way to go. I just thought I'd mention this as a design / interaction / code issue.
If you want to log them in on the subdomain, you can use Authlogic's single use token.
Check out the Params module for an example on logging in with the single use token.
Naturally, your action will log them in and create their session (on the subdomain) so they don't have to re-authenticate for the next request.
There are options to set the domain for the cookie in process_cgi() and session(), but I don't see a way to set those per-request in Authlogic. The authlogic mailing list is pretty responsive, though, and this seems like a pretty standard use-case that someone there would have tried and figured out. And uh, I saw your note on the google group, so never mind that.
If you have an application with multiple subdomains and don't want session cookies to be shared among them, or worse - have a top-level .domain session cookie with the same session_key floating around alongside your subdomain session cookie (Rails will keep one and toss the other - I believe simply based on the order in the request header) - you can use the dispatcher hooks to force the session cookie to subdomains.
Include the hook in ActionController from an extension.
base.send :after_dispatch, :force_session_cookies_to_subdomains
Set the domain this in your after_ dispatch hook.
#env['rack.session.options'] = #env['rack.session.options'].merge(:domain => 'my_sub_domain' end)
For us, we look at the #env[HTTP_HOST] to determine what [my_sub_domain] should be.
With this approach, the user's login must occur at the subdomain for the browser to accept the subdomain'ed cookie (unless using a pattern like the Authlogic Params to propagate to the next request against the subdomain).
Note: The browser will reject the subdomain'ed cookie when the request comes from the higher level domain. For us, this isn't a bad thing - it results in the same outcome that we require, that a top level session cookie doesn't get created and later sent to subdomains.
Another approach to a similar end might be to force a cookie to not be set when not from a subdomain. Not spending much time on it, the way I was able to accomplish this was -
request.env["rack.session"] = ActionController::Session::AbstractStore::SessionHash.new(self, request.env)
in an after filter in ApplicationController.

Resources