brahmana
brahmana

Reputation: 1326

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

How it is currently

And it repeats into a loop until browser says "Website is not redirecting properly".

Upvotes: 3

Views: 1910

Answers (3)

Prometheus
Prometheus

Reputation: 889

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!

Upvotes: 1

brahmana
brahmana

Reputation: 1326

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?

Upvotes: 5

user3618353
user3618353

Reputation: 91

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.

Upvotes: 0

Related Questions