I want to determine if a user just started a new session in my Rails app. I am currently using HTTP referer to define what a new session is. But I am not sure if this is the correct way. The following code is written in Ruby.
if !request.referrer || !request.referrer.start_with?(request.protocol + request.host_with_port)
# If the user came from another site or came here directly, then we regard it as a new session
else
# Not a new session
end
You can store an attribute in the session the first time the user visits. If the attribute is set then you can safely conclude they've visited your website before.
If, however, the attribute is not set then it could mean many things:
the user is visiting your site for the first time
the user may have deleted their cookies, and you can't tell that they're returning
the user may visiting from different browser or on a different device too
This is how you would code it up
if session.has_key?(:visited_before)
# user has visited before
else
# new user
session[:visited_before] = true
end
You should be wary of
where the sessions are being stored and memory limitations, but I wouldn't let this stop you from getting started unless it's a high traffic website.
googlebot and other search engine crawlers, you can detect those in the user agent headers and avoid setting the session.
Related
Because of the awesome EU directive on allowing users opt out from having cookies stored I have the following problem.
I have a message that displays at the top of the screen that asks the user to opt in or opt out.
if the user opts in, cookies are turned on using rack.policy, and the message is hidden. A cookie variable is set to say that the message should not be shown again for future visits. Perfect
If the user opts out. Cookies are disabled, fine. the message is cleared.... and a session variable is set to say don't show the message for this session. This would be fine, but it seems the session variable is saved in the same way as a cookie and the rack policy does not allow it to be displayed. The message therefore flashes up on every single page.
So first, thank you EU. Second, how do I save a "session variable" without saving to a cookie?
Unless you try going for something more exotic like local storage offered by the browser, you can't. The rails guide explicitly states:
All session stores use a cookie to store a unique ID for each session (you must use a cookie, Rails will not allow you to pass the session ID in the URL as this is less secure).
I'm no lawyer, but for what it's worth I believe that law is intended to apply to cookies used for marketing/tracking purposes and does not apply to cookies needed for the site to function like cookies used for authentication or ephemeral storage. With this in mind, you could use the rails's session cookie for must-have data and use separate cookies for other purposes that your application can disable.
I wouldn't take it as gospel, but this Wired article seems to a good job of providing a simplified explanation.
This is an extremely weird situation. I have a Rails 3 app that authenticates users using OmniAuth. The problem I recently encountered is that after correctly validating a user using the aforementioned gem, at some part of the code I set some session variables, specifically:
session[:user_id] = user.id
The problem is, that this line only works for some users (it does not depend on the browser, the cookies, nothing, only for certain users. The user variable is valid and has an id attribute, but this session variable does not persist on the next request, sending this user back to the login page. Again, this only happens to some users, which makes the problem a whole lot more difficult to debug.
Any ideas? Thanks in advance!
PS: I've tried using the cookie session store and the database session store, both with the same results.
EDIT: The problem ended up being something completely not related to sessions.
I have an experience with CakePHP and now started coding on ASP.NET MVC framework.
I have a problem with the login system. How can I restrict users from logging only one time simultaneously on my system?
I can create a field in my DB where Customer becomes active when logs in. If he logs out I can make active false. But what if the session just ends? How can I catch this?
This article provides a possible solution.
This is, unfortunately, something of a challenge due to the way that the session end event are implemented as you don't have access to the information you need when they fire.
So turn the problem on its head a little, if you track the session that they last logged in on then if you get a request from that same authenticated user in a different session then remove the auth for that session (in effect the older session) with an appropriate redirect to a suitable message.
The key here is tracking not only who is currently logged in but also the session ID for that login.
Details are a bit more complicated - but you can perform the test at a request level or by adding your own base page class, deriving all your "real" pages from that and checking in a page event.
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.
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.