Anyway, we're using the splendid Authlogic plug-in for all the authentication duties and while it does many things very well it doesn't have a built-in way to restrict simultaneous logins. I asked around and got a couple of tips on what might work and how I might proceed. It was a simple process but after several others had the same problem I thought I would formalize the "solution" in a blog post in hopes that it might help someone else in the future.
- Step One - Add a place to store a session key
# add_session_key_to_users.rb class AddSessionKeyToUsers < ActiveRecord::Migration def self.up add_column :users, :session_key, :string end def self.down remove_column :users, :session_key end end
This gives us a place to store a "session_key" value that changes every time a user logs in. - Step Two - Insert the "session_key" on login
# user_sessions_controller.rb def create ... if @user_session.save # Save the session ID to detect simultaneous login attempts @user_session.record.session_key = session[:session_id] @user_session.record.save! ... end end
What this does is forces the session_id to be saved to the User model each time a user logs into the site. We'll check this value later to make sure the session hasn't changed. I'm using the session_id here as a "unique" value but it could be anything you want; timestamp, IP address, etc...
- Step Three - Make sure the user is unique
# application_controller.rb def current_user ... # Prevent simultaneous logins if @current_user && @current_user.session_key != session[:session_id] flash[:notice] = 'Access denied. Simultaneous logins detected.' current_user_session.destroy end end
Here is where the rubber meets the road, so to speak. In Authlogic, the current_user method is accessed on every page request so it is the perfect place to check for duplicate user sessions. We simple verify that the session_id in the users cookie is the same one in the database. If they are different we destroy the session and update the flash message.
This is a pretty simple little hack but it seems to work OK. One "problem" that I have noticed is that while the session is immediately destroyed the current request continues unabated. This means that the two users could possibly perform two actions at the same time, but it shouldn't be a problem. Another side effect of this technique is that the last user to log in always gets the session. This might be a problem for you but it wasn't for my project.

6 comments:
Thanks for this post! I seem to be following you by a few days in the authlogic google group.
BTW, I was able to move setting saving the user's session_id to a callback in the UserSession:
class UserSession < Authlogic::Session::Base
after_save :set_session_id
def set_session_id
record.session_id = controller.session.session_id
end
end
I discovered that the record save is not even necessary because Authlogic does it automatically. I am still learning my way around Atuhlogic so I may have missed something about doing it this way but I like that it keeps the controller skinny.
Ah, using a filter is even better than my solution! I love it when we all work together. :)
great msg for me, thanks a lot dude˙﹏˙
hi, have any of you experienced problems with the session_id length? since the migration uses :string, at least for mysql, a varchar with 255 characters is created...
we found that some session_ids are bigger than that and wanted to ask if the simple solution of making the sql field bigger is the right way to go?
thanks a lot
Actually, there is something you could use in authlogic, if you reset presistence token you can achieve the same behaviour. Like this:
class UserSession < Authlogic::Session::Base
before_create :reset_persistence_token
def reset_persistence_token
record.reset_persistence_token
end
end
By doing this, old sessions for a user loggin in are invalidated.
Just a big thanks for posting this and for Rafael with his even simpler solution. Saved me a lot of time and trouble. Thanks!
Post a Comment