Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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".

like image 370
brahmana Avatar asked Nov 20 '25 15:11

brahmana


1 Answers

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

  1. User logs in. Session is set with user id.
  2. User uses. Session is updated with user id.
  3. User idles (at least for timeout period)
  4. User makes a request. Request is sent with same session.
  5. Session is identified as timed out. Clear the session and redirect back to same page.
  6. Browser visits same page again.
  7. No user in the session. Redirect to login page.

How it happened in my case

  1. User logs in. Session is set with user id.
  2. User uses. Session is updated with user id.
  3. User idles (at least for timeout period)
  4. User makes a request. Request is sent with same session.
  5. Session is identified as timed out.
  6. 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)
  7. Redirect back to same page. Browser keeps the session cookie unaltered.
  8. 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.

Devise Timeout Flow

@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?

like image 142
brahmana Avatar answered Nov 22 '25 04:11

brahmana