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.
Related
Not talking anything super sensitive or private. I have a situation where I just want to restrict access, and was going to use Rails' authenticate_or_request_with_http_basic method to require a password on a page. Then I realized, I could also just do that by having an extra parameter on the URL and comparing it with whatever I define. So something like:
www.mypage.com?secret=password
def page
if params[:secret] == ENV["secret"]
...
end
end
From a security perspective, why would this not be as secure as the Rails' method? What added security does the Rails' method provide?
The level of security would be almost identical to basic authentication, except instead of two fields, you only need one.
Basic authentication provides no confidentiality for the username and password being passed in the header, unless you're working over HTTPS, which you should be anyways and this will be encrypted over HTTPS as well.
The only thing I'd say is that the URL could end up in the browser history and will show up in the address bar. So I guess in that way it is less secure, since if someone could gain access to your browser history, they could get the key.
I guess you just have to decide how secure you care for it to be.
I will add that Devise is a great Gem for managing authentication (kind of the standard, go to solution) and there are many great plugins for it to manage permissions as well on a user level basis. Although, I sense that it may be overkill for what you want.
I'm working on a Rails 4.2 Application and using devise gem for authentication.
For remember_me feature, devise generates a cookie remember_user_token which gets destroy after sign_out.
Is there a way such that Devise should not destroy remember_user_token ?
I tried to false the below config in the initializer
config.expire_all_remember_me_on_sign_out = false
But it didn't help.
I need that cookie after sign-out such that it will populate the login form.
Please help.
Thanks
Coupling authentication with form pre-filling isn't necessarily a good idea. You can store the login in a cookie upon successful login. You can override the create method in your SessionsController, call super to call Devise::SessionsController#create and pass it a block. The block will be executed after a successful log in and will receive the user as a parameter.
class SessionsController < Devise::SessionsController
def create
super do |user|
cookies[:login] = user.login
end
end
end
Here is the low down on cookie store. First off, everything in a cookie is there permanently once it's set or until the user deletes the cookie manually somehow. This means, that if you set user_id and user_group_id, it's there for good in the cookie until updated or deleted. This is different from a session since the session is like ram on a computer, once the browser is closed, the session closes with it as well as all of it's data.
So, this means that when you log out your user, you need to specify that their cookie empties anything you don't wan't it to have. When your user logs in, you set anything that you want the user to have while they are logged in. So, since the session and cookie are separate things completely, they never interact together unless you choose to make them. So your session will never dump its self into the cookie store unless you make it do that.
Every time your users go to your site, you could have a single handshake that makes sure that the cookie matches the db if necessary. Otherwise, you could have differing data what only gets updated on login or what not and without the handshake, the user would have to keep logging in to make sure they are still valid which defeats the purpose of having a cookie in the first place.
The downside of client side cookie storage is security concerns. Depending on how you use the cookie to store data, a person could hijack somebodies cookie on your site and pretend they are them. This can be avoided by careful design, but just assume that whatever is in your cookie store is fair game to everybody so use it carefully and for only non secret data.
Hope this helps!
Setup
I have a Rails application where users register for an account, and a subdomain is created for them. They can then proceed to the subdomain and log in with their credentials. The workflow looks something like this:
User visits base domain fills out a form that with email/username/password and subdomain fields
From the submitted info, the server creates an account in the global/public database. Server then creates a database that will be specific to that particular subdomain/account, and stores the user record in it.
User is redirected to their subdomain, and asked to log in.
(note: to implement the separate "databases", I'm using postgres schemas, but that should be irrelevant.)
The question
My question involves step 3. I would like to redirect the user to their subdomain and log them in automatically instead of asking them to log in. However, I do not want to share a single session across all of the subdomains.
I would like to somehow securely transmit auto login request.
Possible Solution
I have considered using a single-use, random token that I would store in a cookie and in the users table. After the user successfully creates an account, he would be redirected to the subdomain. At that point the token would be consumed/destroyed and the user would be automatically logged in.
I would also need to have a short window for the token to be used before expiring.
Thoughts? Thanks!
I had the same issue, the possible solution you suggest does not work because the session is not shared between subdomains.
I solved it the following way (same idea you propossed, different implementation):
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.
My application has a userspace which used to be accessed by a url like domain.com/~username, but I am in the process of converting that to using subdomains instead (username.domain.com). However, I am running into an issue that I'm hoping someone might have an idea of how to get around.
Currently, visitors to a user's site get a cookie of the form user<id>_authentication (where <id> is the user ID of the site they're visiting), which is set to have the domain www.domain.com. However, now that I'm switching to subdomains, I want to find those cookies and transfer them to a new cookie called authentication per subdomain, using the subdomain as the cookie domain. However, the rails cookies array does not find the main domain cookies.
I know that if the old cookies were using .domain.com as the domain instead, they'd apply to the subdomain and would be present in cookies, but these cookies are already existing, and I'm trying to make the change as seamless for a user as possible -- so if they had an authentication cookie already for a site, I want them to not have to reauthenticate if at all possible.
Is there any way I can get the cookies from the main domain or does anyone have another suggestion of how I can transfer the cookies?
Update: Sorry, I didn't make it clear before, the cookie is only set if the visitor actively authenticates themselves by submitting a form on the user's site.
If you change the cookie domain to be more permissive (applying to more sub domains) you have no way to read the old, more restricted cookies except from the top level domain that used to work.
You will have to read the cookie, authenticate, and then write a new more permissive cookie before the cookie can be read by the subdomain.
You can roll out your migration logic in advance of the feature and hope you get most people. The rest will have to re-authenticate manually.
Personally I think they should have to re-authenticate.. it will only happen once, then they'll have the new ".domain.com" cookie.
But... One way to achieve this would be to check for the new cookie and when failing to find it, redirect to a new page on the main domain, providing the return url.
In that new page, check for the old style cookie, set the new style cookie, and redirect to the original url. if they don't have the old style cookie, redirect to the login area.
hope this helps.
My Rails-app has a sign in box with a "remember me" checkbox. Users who check that box should remain logged in even after closing their browser. I'm keeping track of whether users are logged in by storing their id in the user's session.
But sessions are implemented in Rails as session cookies, which are not persistent. I can make them persistent:
class ApplicationController < ActionController::Base
before_filter :update_session_expiration_date
private
def update_session_expiration_date
options = ActionController::Base.session_options
unless options[:session_expires]
options[:session_expires] = 1.year.from_now
end
end
end
But that seems like a hack, which is surprising for such common functionality. Is there any better way?
Edit
Gareth's answer is pretty good, but I would still like an answer from someone familiar with Rails 2 (because of it's unique CookieSessionStore).
You should almost certainly not be extending the session cookie to be long lived.
Although not dealing specifically with rails this article goes to some length to explain 'remember me' best practices.
In summary though you should:
Add an extra column to the user table to accept a large random value
Set a long lived cookie on the client which combines the user id and the random value
When a new session starts, check for the existence of the id/value cookie and authenticate the new user if they match.
The author also recommends invalidating the random value and resetting the cookie at every login. Personally I don't like that as you then can't stay logged into a site on two computers. I would tend to make sure my password changing function also reset the random value thus locking out sessions on other machines.
As a final note, the advice he gives on making certain functions (password change/email change etc) unavailable to auto authenticated sessions is well worth following but rarely seen in the real world.
I have spent a while thinking about this and came to some conclusions. Rails session cookies are tamper-proof by default, so you really don't have to worry about a cookie being modified on the client end.
Here is what I've done:
Session cookie is set to be long-lived (6 months or so)
Inside the session store
An 'expires on' date that is set to login + 24 hours
user id
Authenticated = true so I can allow for anonymous user sesssions (not dangerous because of the cookie tamper protection)
I add a before_filter in the Application Controller that checks the 'expires on' part of the session.
When the user checks the "Remember Me" box, I just set the session[:expireson] date to be login + 2 weeks. No one can steal the cookie and stay logged in forever or masquerade as another user because the rails session cookie is tamper-proof.
I would suggest that you either take a look at the RESTful_Authentication plug in, which has an implementation of this, or just switch your implementation to use the RESTful Authentication_plugin. There is a good explanation about how to use this plug in at Railscasts:
railscasts #67 restful_authentication
Here is a link to the plugin itself
restful_authentication
The restful_authentication plugin has a good implementation of this:
http://agilewebdevelopment.com/plugins/restful_authentication
Note that you don't want to persist their session, just their identity. You'll create a fresh session for them when they return to your site. Generally you just assign a GUID to the user, write that to their cookie, then use it to look them up when they come back. Don't use their login name or user ID for the token as it could easily be guessed and allow crafty visitors to hijack other users' accounts.
This worked like a charm for me:
http://squarewheel.wordpress.com/2007/11/03/session-cookie-expiration-time-in-rails/
Now my CookieStore sessions expire after two weeks, whereby the user must submit their login credentials again in order to be persistently logged-in for another two weeks.
Bascially, it's as simple as:
including one file in vendor/plugins directory
set session expiry value in application controller using just one line
I would go for Devise for a brilliant authentication solution for rails.