Restful Authentication: Allow logins from multiple computers?
Our Rails app is using Restful Authentication for user/session management and it seems that logging in to the same account from multiple computers kills the session on the other computers, thus killing the "Remember me" feature.
So say I'm at home and log in to the app (and check "Remember me"). Then I g开发者_如何学编程o to the office and log in (and also check "Remember me"). Then, when I return home, I return to the app and and have to re-log in.
How can I allow logging in from multiple machines and keep the "Remember me" functionality working across them all?
You are going to sacrifice some security by doing this, but it's definitely possible. There are two ways you should be able to accomplish this.
In the first, you can override the make_token method in your user model. The model is currently implemented as follows.
def make_token
secure_digest(Time.now, (1..10).map{ rand.to_s })
end
Every time a user logs in, with or without a cookie, the make_token
method is called which generates and saves a new remember_token
for the user. If you had some other value that was unique to the user that couldn't be guessed, you could replace the make_token
method.
def make_token
secure_digest(self.some_secret_constant_value)
end
This would ensure that the token never changes, but it would also enable anyone that got the token to impersonate the user.
Other than this, if you take a look at the handle_remember_cookie!
method in the authenticated_system.rb
file, you should be able to change this method to work for you.
def handle_remember_cookie!(new_cookie_flag)
return unless @current_<%= file_name %>
case
when valid_remember_cookie? then @current_<%= file_name %>.refresh_token # keeping same expiry date
when new_cookie_flag then @current_<%= file_name %>.remember_me
else @current_<%= file_name %>.forget_me
end
send_remember_cookie!
end
You'll notice that this method calls three methods in the user model, refresh_token
, remember_me
, and forget_me
.
def remember_me
remember_me_for 2.weeks
end
def remember_me_for(time)
remember_me_until time.from_now.utc
end
def remember_me_until(time)
self.remember_token_expires_at = time
self.remember_token = self.class.make_token
save(false)
end
#
# Deletes the server-side record of the authentication token. The
# client-side (browser cookie) and server-side (this remember_token) must
# always be deleted together.
#
def forget_me
self.remember_token_expires_at = nil
self.remember_token = nil
save(false)
end
# refresh token (keeping same expires_at) if it exists
def refresh_token
if remember_token?
self.remember_token = self.class.make_token
save(false)
end
end
All three of these methods reset the token. forget_me
sets it to nil
, whereas the other two set it to the value returned by make_token
. You can override these methods in the user model, to prevent them from resetting the token if it exists and isn't expired. That is probably the best approach, or you could add some additional logic to the handle_remember_cookie!
method, though that would likely be more work.
If I were you, I would override remember_me_until
, forget_me
, and refresh_token
in the user model. The following should work.
def remember_me_until(time)
if remember_token?
# a token already exists and isn't expired, so don't bother resetting it
true
else
self.remember_token_expires_at = time
self.remember_token = self.class.make_token
save(false)
end
end
#
# Deletes the server-side record of the authentication token. The
# client-side (browser cookie) and server-side (this remember_token) must
# always be deleted together.
#
def forget_me
# another computer may be using the token, so don't throw it out
true
end
# refresh token (keeping same expires_at) if it exists
def refresh_token
if remember_token?
# don't change the token, so there is nothing to save
true
end
end
Note that by doing this, you're taking out the features that protect you from token stealing. But that's a cost benefit decision you can make.
You can change what the remember_token
is to achieve this. You can set it to:
self.remember_token = encrypt("#{email}--extrajunkcharsforencryption")
instead of
self.remember_token = encrypt("#{email}--#{remember_token_expires_at}")
Now there is nothing computer or time specific about the token and you can stay logged in from multiple machines.
精彩评论