Double sign-in required with devise - ruby-on-rails

I've got a Rails 3.0.9 application using Devise 1.4.9. I'm having a bit of a problem with the login screens. I think I understand the problem as I've previously fixed a similar issue in a C application. But this time I'm just using devise so it is harder to just fix the source code ...
The basic pattern is I log out of the application, which takes me to a URL such as this: http://10.0.0.25:3000/devise/users/sign_in
I then go home and come back to work the next day, with the above address still open in the browser. I type in the password, but I just get a message saying my session has expired, and I have to re-enter the password.
Making an educated guess, when the user is shown the sign_in page, devise creates a new session which is not currently logged in. When the user submits the page, devise checks the session exists, and then checks the credentials. For security reasons, the credentials will not work for an expired (or unknown) session.
The fix in the C application was to allow a very long timeout for sessions that had never been logged in. Once a session is logged in, it does need to be logged out after an inactivity delay that is relatively short, so just changing config.timeout_in wouldn't be enough.
EDIT: I've noticed by messing around with the timeout set down to 1 minute that the not-logged-in session timeout does not change to one minute (in fact I haven't really noticed whether it has changed at all...) So there must be something else that does this.
Also I realised when a session is not logged in, there is no time stored within the session cookie, so I don't even know exactly how the server determines the session age (I don't have a server-side sessions table).

Related

Why does devise redirect to current path when session times out

The code here shows that devise will redirect to the currently requested path when the session times out (which is checked and enforced by the timeoutable module) : https://github.com/plataformatec/devise/blob/master/lib/devise/failure_app.rb#L120
The attempted_path is set by warden before invoking the failure app.
Question is : Why would devise redirect back to the current requested path itself? If the session has timed out then shouldn't the client be redirected to the login page for the current entity (User or Admin or whatever)?
It does use the scope_url if attempted_path is not set. But I do not understand why should a redirect be made to the currently requested path again? Wouldn't this just result in a redirect-loop?
This redirect-loop is infact happening with Rails admin. If I enable timeoutable for the model for which I am authenticating in Rails admin, then after session timeout, any request will result in a redirect loop.
So can someone please explain to me why a redirect to attempted_path is being made at all? What use case doe sit serve?
Additional info
Here are the two flows that I have in mind.
How it should be
User tries to access page x. Session is timed out.
User is redirected to the login page
User logs in
User is redirected back to page x
How it is currently
User tries to access page x. Session is timed out.
User is redirected to page x.
And it repeats into a loop until browser says "Website is not redirecting properly".
After a long "debugging weekend" I found out that the issue was because the Session and Cookie middlewares were placed after Warden in the rack stack.
My application is a Rails 5 API application, which means cookies and sessions are not available by default. Despite being an API app, I had to incorporate session / cookie based auth mechanism for certain reasons. So I manually added the two middlewares to the rack stack.
Since I added them in config/application.rb they got added almost at the far end of the stack, i.e. much after the Warden middleware itself. However the Warden middleware makes it very clear that it needs a Session and Cookie manager before it in the stack. That way any session changes it makes will get serialized into the session and the cookie eventually.
This resulted in the session changes done by the failure app being discarded. Because of that the session never got cleared and resulted in a redirect loop. The following steps will make it clearer.
How it should have been
User logs in. Session is set with user id.
User uses. Session is updated with user id.
User idles (at least for timeout period)
User makes a request. Request is sent with same session.
Session is identified as timed out. Clear the session and redirect back to same page.
Browser visits same page again.
No user in the session. Redirect to login page.
How it happened in my case
User logs in. Session is set with user id.
User uses. Session is updated with user id.
User idles (at least for timeout period)
User makes a request. Request is sent with same session.
Session is identified as timed out.
Session is cleared but the cleared session is never serialized back to the cookie. (This happens because in case of auth failure control goes directly back to Warden middleware bypassing all the intermediate middlewares it came through. So it misses the cookie and session middlewares)
Redirect back to same page. Browser keeps the session cookie unaltered.
Browser visits same page again with the same session cookie
Steps 5-8 repeat until browser stops with an error.
Here is a sequence diagram I made capturing the whole flow for anyone interested in the details.
#Prometheous : Thank you for your comment. However one thing is still unclear to me :
In case of a timeout, what issues will be there if the FailureApp directly redirects to scope login url. You say :
Without the redirection to the attempted path, devise wouldn't know
how to redirect to the sign in page.
But, can't it get it from the scope_url method which is used in the else part here : https://github.com/plataformatec/devise/blob/master/lib/devise/failure_app.rb#L128 ?
scope is known for sure.
What am I missing?
My guess is that session is timed out and user is asked to sign in again.
User tries to access page x.
Turns out the user sessions is timed out.
User login again.
User returns to page x.
The timeout login session message is displayed in the action requested.
Glad i could somehow help you! You did an amazing job analyzing the whole devise loop process!
However, i think you are dealing either with a really deep nested bug, or devise isnt setup properly. I tested it on a bigger project by myself and it worked just fine.
In Devise.rb i uncommented:
config.timeout_in = 10.seconds
and changed for testing purposes the timeout to 10 seconds.
I have a FAQ page on this project, which has a page_controller. Inside page_controller i added:
before_action :authenticate_user!
Inside my Devise model, in this case User, i added:
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:omniauthable, :timeoutable
When i sign in a user, go to the FAQ page, wait for 10 seconds, refresh the page, i get redirected to the sign in page and a message that my session was timed out. When i sign in the user again, i get redirected to the FAQ page, without any additional code than the few steps i showed above.
The flow is (which you probably understand much better than me) like that: Check if user is signed in -> if not, redirect. Now the redirect doesn't have anything to do with the :timeoutable module. The :timeoutable module 'simply' counts down and checks if a user session is valid, if not, well it logouts the user in the background. If the user wants to attempt the page than again, it uses the :authenticate_user! method, checks if the user is signed in, if not, well, redirect him.
It seems like your authenticate_user! is not working the way it should. Have you tried (in development) to update devise?
I highly recommend you to create a super simple app with devise and redo the steps from above and see if it works the way you wanted.
The redirect_url part is normally injected behind the scenes by devise, as far as i know.
I once costumized it for an app, by going to config/initializers/devise.rb
drop inside Devise.setup |config|
require "custom_path"
config.warden do |manager|
manager.failure_app = CustomPath
end
than, in lib/custom_path.rb
class CustomPath < Devise::FailureApp
def redirect_url
## redirect to wherever you want
end
end
thats it. Devise will then redirect to whatever page you want.
Anyways, glad you could still solve the problem by tweaking some parts in the middleware.
Greetings!

How to detect if a user has another user's session in Rails?

We are experiencing a bizarre, very rarely occurring bug where a user will be logged into another user's account.
We are on Rails 4.2. We use authlogic for authentication and dalli as our memcached client. Use memcache as the session store.
I haven't been able to figure out what is causing the issue, but the worst part is that even if I did have a hypothesis I wouldn't know how to confirm if it worked or not.
I would like to find some way to log if a user has been given the wrong session, both to help debug the problem and to determine if a potential fix works.
I'm just not sure if it's possible. If the user's cookie has the wrong session ID, how can I possibly figure that out?
Try going back to signed, encrypted cookie session store. Use memcached for frequently accessed items, like the user record. Load the user model from memcached instead of the database.
If you really want to log session hijacking, then log the user's IP address. If the IP address suddenly changes, as if they were logged in one place, then all of a sudden are making requests from another place, then maybe another user hijacked their session cookie?
http://guides.rubyonrails.org/v5.0/security.html#session-hijacking
Be aware that using TOR would show that pattern, as it generates a new route every ten minutes, but doesn't mean the session was stolen or mixed up.
If you are not using signed or encrypted cookies, then it allows Javascript or malicious ads to steal the session id, and send it back to the attacker's server.
It could also be your session ids are not secure or random enough. Maybe a new session id overwrites another session id in memcached? Since you are using a different session store, maybe you customized the session identifier?

How can I logoff a specific user in a Devise powered Rails app?

By default, the mechanism in Rails seems to be setting a session cookie, to keep track if a user's logged in or not. While this would ensure that a user is logged off when he closes the browser, I also need him to be logged of in case of inactivity. I have the following use case:
user is logged in
he is on a specific page ( websockets powered, no refresh )
client pings server, in order to let the server know he's still active
in case more than X number of minutes go by without any sign of activity, the user should be logged off
How can such a user case be handled? I thought about setting an expiration time of a few minutes on the cookie. I'd like it that the cookie's expiration time is set back to the original duration, every time the client pings the server.
I'm open to any ideas on how this problem could be tackled.
If you use devise it's already there and you can configure timeout duration in devise.rb https://github.com/plataformatec/devise/blob/master/lib/generators/templates/devise.rb#L132
Use the module Timeoutable. Read something about it in the doc : http://github.com/plataformatec/devise.
Info: migrated comment to answer.

ASP.NET MVC ActiveDirectoryMembershipProvider user stays logged in even when password has changed

I am using ActiveDirectoryMembershipProvider in my web app. I authenticate users with their domain credentials like so
if (Membership.ValidateUser(m.Username, m.Password))
FormsAuthentication.SetAuthCookie(m.Username, true);
This works well.
But even when the user's password is changed in active directory, the user stays logged in to the web app?
How can I ensure the user does not stay logged in to the web app if their domain password changes, or their account is disabled etc?
The answer is to periodically (every 30 minutes or so) check User.IsApproved and User.LastPasswordChangedDate to make sure the users credentials are still valid.
To do this you need to manually create the FormsAuthenticationTicket and cookie, rather than using FormsAuthentication.SetAuthCookie.
Put the date you validated the user inside UserData and compare this against LastPasswordChangedDate.
I've implemented this and it works perfectly.
More information here
Check if Active Directory password is different from cookie
I'm not 100% certain, but it sounds like you're unhappy that the user's auth ticket continues to work even though their password changes / account expires.
Once a user has logged in and has a authentication ticket (cookie), the user is not challenged for authentication again until until the ticket expires (set in the web.config file). Here are 2 suggestions for dealing with this problem:
Wait for the auth ticket (cookie) to expire. Upon the next login, the user will
be required to use their new password. Variations of this solution include using session-only cookies so that the user must always login when the browser is closed (recommended for AD authentication).
Write an Http Module that looks for a list of recently updated users and inspects the auth ticket early in the HTTP pipeline. If an auth ticket comes through and matches the list of updated users, you exprire the user's cookie and re-direct them to the login page. Here's a similar question that would help get you started:
How can I force a logout of all users on a web site

Cookies getting deleted from browser on exit (Spring Security 3.1)?

I am using Spring Security 3.1 to handle login authentication, session timeouts and maximum sessions.
Also I am deleting cookies only on logout.
<logout delete-cookies="JSESSIONID" logout-success-url='logout page' />
Also I have set maximum sessions to 1 as of now for testing.
When I open my webpage in browser, it stores jsession id in cookie but the problem starts when I exit and reopen my browser. At this time I cannot find any cookies in the browser, they get deleted that is why I am not getting redirected to welcome page(page after login).
But when I login again, it shows an error message that I am printing:-number of sessions exceeded.
This possibly means that session remains alive on server side but it gets deleted from the cookie on client side due to which I neither see the welcome page nor am able to login on the login page.
What else I need to do so that cookies remain there in the browser till the session times out? I have set session timeout to 10 days
This is normal behaviour. JSESSIONID cookies are only valid for the lifetime of a browser session so are gone when you close your browser. This isn't something you can change.
There is no connection between the browser's perception of a session and the lifetime of a session on the server. Unless you actually log out, the server session is still there until it times out and is removed by the server (10 days in your case). Until that happens, trying to log in again will exceed the number of allowed sessions.
If you want to stay logged in for 10 days, you might want to look at using remember-me cookies rather than the standard servlet container session.
Unless you have a definite requirement for restricting the number of concurrent sessions a user can have, I would avoid using that as it will just cause you problems. You haven't actually shown your configuration for this, but there are really just two options. Either a user can log back in again and the previous session will be marked as expired, or attempting to log in a second time will cause an error until the previous session has timed out, or the user logs out to explicitly invalidate it. The behaviour is controlled by the error-if-maximum-exceeded namespace attribute.

Resources