Rails/Devise - Determining when user session will expire
Here is how you do it:
class User < ActiveRecord::Base
devise :authenticatable, :timeoutable, :validatable, :timeout_in => 20.minutes
end
How to get Devise session timeout callback?
I found that doing Warden.before_logout was the best solution:
# app/models/user.rb
Warden::Manager.before_logout do |user, auth, opts|
#fdsafdsafdsa
end
Unfortunately, there doesn't seem to be any way to do this with pure Devise.
Why does devise redirect to current path when session times out
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?
Related Topics
How to Make a Ruby Enumerator That Does Lazy Iteration Through Two Other Enumerators
Ruby Getting the Longest Word of a Sentence
Ruby Instance_Eval on a Class with Attr_Accessor
How to Catch Top Level Failures on an Eventmachine Server
Case-Insensitive Find_Or_Create_By_Whatever
Argumenterror: Wrong Number of Arguments (1 for 2)
How to Flatten Double Arrays in Mongodb
Actioncontroller::Routingerror (No Route Matches [Get] "/"):
Finding the Date for a Given Week Number
Fresh Install of Rails and Getting Openssl Errors: "Already Initialized Constant Openssl"
Ruby's Argv Can Be Empty on Windows Depending on a Way to Run Script
What Regex How to Use to Get the Domain Name from a Url in Ruby
How to Search for Useful Ruby Gems