waiting-for-dev/devise-jwt

User gets logged out after an hour of inactivity, whether or not :rememberable is used

unikitty37 opened this issue · 4 comments

I'm trying to write a Rails backend that uses devise-jwt to manage logins from a Vue frontend. However, after an incredibly short period of inactivity, the user is told they are logged out.

I would like to configure it so that the user can be inactive for 24 hours before being automatically logged out if they did not check "Remember Me", and for 6 weeks if they did. When the app was all-Rails, and just using Devise, the code I had behaved correctly. However, it does not seem to work as expected with devise-jwt, and I'm not sure if this is something I've done wrong or a limitation of the way JWT works.

I have the following in my Devise initialiser:

  # ==> Configuration for :rememberable
  config.remember_for = 6.weeks
  config.expire_all_remember_me_on_sign_out = true
  config.extend_remember_period = true

and in my User model:

  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable,
         :lockable, :confirmable,
         :jwt_authenticatable, jwt_revocation_strategy: JwtDenylist

I do not have :timeoutable enabled (I did originally, and thought that was the cause of the problem — but I have just been logged out after about an hour of inactivity).

The JSON being passed on login is

{
  "user": {
    "email": "username@example.com",
    "password": "superdupersecret",
    "remember_me": 1
  }
}

Expected behavior

The user is not logged out unless one of the following is true:

  • 24 hours have elapsed since the last API call made with that token, and they logged in without passing remember_me: 1.
  • 6 weeks have elapsed since the last API call made with that token, and they passed remember_me: 1 when logging in.
  • The user explicitly clicked the "log out" button.

Actual behavior

After about one hour since the last access, the user is reported as being logged out.

Steps to Reproduce the Problem

  1. Configure devise-jwt with the options above.
  2. Create a user with email username@example.com and password superdupersecret .
  3. Make a POST to the route that points at sessions#create, passing the JSON above.
  4. Verify the user is logged in.
  5. Wait a couple of hours.

Debugging information

Provide following information. Please, format pasted output as code. Feel free to remove the secret key value.

  • Version of devise-jwt in use: 0.8.0
  • Version of rails in use: 6.0.3.2
  • Version of warden-jwt_auth in use: 0.5.0
  • Output of Devise::JWT.config
=> #<Dry::Configurable::Config values={:secret=>"REDACTED", :expiration_time=>3600, :dispatch_requests=>[], :revocation_requests=>[], :aud_header=>"JWT_AUD", :request_formats=>{}}>
  • Output of Warden::JWTAuth.config
=> #<Dry::Configurable::Config values={:secret=>"REDACTED", :algorithm=>"HS256", :expiration_time=>3600, :aud_header=>"JWT_AUD", :mappings=>{:user=>User (call 'User.connection' to establish a connection)}, :dispatch_requests=>[["POST", /^\/api\/v1\/users\/login$/], ["POST", /^\/api\/v1\/users$/]], :revocation_requests=>[["DELETE", /^\/api\/v1\/users\/logout$/]], :revocation_strategies=>{:user=>JwtDenylist (call 'JwtDenylist.connection' to establish a connection)}}>
  • Output of Devise.mappings
=> {:user=>
  #<Devise::Mapping:0x000055ace4fb5658
   @class_name="User",
   @controllers=
    {:registrations=>"registrations",
     :sessions=>"sessions",
     :passwords=>"devise/passwords",
     :confirmations=>"devise/confirmations",
     :unlocks=>"devise/unlocks"},
   @failure_app=Devise::FailureApp,
   @format=nil,
   @klass=#<Devise::Getter:0x000055ace4fb50b8 @name="User">,
   @modules=[:database_authenticatable, :rememberable, :recoverable, :registerable, :validatable, :confirmable, :lockable, :trackable, :jwt_authenticatable],
   @path="users",
   @path_names=
    {:registration=>"",
     :new=>"new",
     :edit=>"edit",
     :sign_in=>"login",
     :sign_out=>"logout",
     :password=>"password",
     :sign_up=>"sign_up",
     :cancel=>"cancel",
     :confirmation=>"confirmation",
     :unlock=>"unlock"},
   @path_prefix="/api/v1",
   @router_name=nil,
   @routes=[:session, :password, :registration, :confirmation, :unlock],
   @scoped_path="users",
   @sign_out_via=:delete,
   @singular=:user,
   @used_helpers=[:session, :password, :registration, :confirmation, :unlock],
   @used_routes=[:session, :password, :registration, :confirmation, :unlock]>}

You can configure the expiration time of your tokens:

https://github.com/waiting-for-dev/devise-jwt#expiration_time

Rememberable works with cookies, so it makes no sense in the context of JWT tokens.

If you need more persistence with tokens, you're probably better off with something like OAuth.

Thanks — I think the problem is that I don't want users (myself included) having to log in all the time, but I had got the impression that setting the expiration time to something in the order of weeks was a security risk.

The only other way I can think of would be storing the user's credentials in the browser's local storage and retrying the login with the saved credentials if a 401 is returned, but that seems even less secure…

I don't really want to implement OAuth here, as it seems very complicated for a simple site (plus, sooner or later, someone will ask for Facebook support — and I don't do Facebook :)

Incidentally, is the expiration time extendable? As I was using config.extend_remember_period = true, I would want to have the token expire after it hasn't been used for a certain amount of time, rather than a fixed period from its inception…

No, it isn't. The expiration time is hardwired in the token's payload.