waiting-for-dev/devise-jwt

Option to prevent dispatch of a new token with a previous valid token

Opened this issue · 3 comments

I found a problem (for me, it may not be considered a problem for others) that I do not see how I can solve properly.
If I do an initial authentication with email/password to retrieve a JWT token, I can infinitely retrieve a new fresh token using the previous token on the login endpoint.
Some can see this as a feature to keep a user authenticated forever. For me, this can be a security problem, if an attacker steals a valid JWT token he can keep a valid authentication forever by renewing the token regularly.

Some context of what the gem is doing. For the JWT authentication to work, https://github.com/waiting-for-dev/warden-jwt_auth/blob/master/lib/warden/jwt_auth/strategy.rb was added. Any request with a valid JWT token can be authenticated, including the login endpoint. By default, the login endpoint is configured to dispatch a new JWT token. So by going to the login endpoint with a valid JWT token (instead of email/password) a new fresh token can be retrieved.

I'm looking for a way to prevent this automatic renewal, and only allow authentication with email/password on the login endpoint.
My first approach was to override the Devise controller to force authentication with only user/password, something like

warden.authenticate!(:database_authenticatable, scope: :user)

This kind of works, but I don't consider it safe. I had a bug in my code where I had a controller callback retrieving a current_user.id for logging. By calling current_user the authentication was being performed before I was expecting and the above line was effectively not doing anything because the user was already authenticated. Because of this bug, the JWT could still be renewed with another JWT.

My next idea was to not have the automatic token dispatch and do it manually, this way I could add my own validations and only dispatch the token when I wanted. But looking at warden-jwt_auth seems that the token dispatch is tied to warden after_set_user callback and there isn't an easy way of calling the dispatch manually.

Is there any other way that I do not see to prevent the renewal of the JWT token with another token?

Hi!

What I did is I use a blacklist of jwt tokens per user like this:

  devise :database_authenticatable, :registerable, :recoverable,
         :jwt_authenticatable, :lockable, jwt_revocation_strategy: self
  def revoke_jwt(payload)
    revoked_jwts << payload['jti']
    save!
  end

  def jwt_revoked?(payload)
    revoked_jwts.include?(payload['jti'])
  end

with this colomn in user :

add_column :users, :revoked_jwts, :jsonb, default: []

and in the devise sessions controller edit methods for create and destroy :)
If it doesn't work properly, don't blame me, I'm a junior ahah

Thanks @sergiopatricio and sorry for the very late reply here. I agree your concern is valid. Ideally, we could find a way for the strategy to return false when the request is against the login endpoint. I'll experiment with that idea once I have some free time, but please go ahead if you have the availability now.

I'm exploring another solution in my code which is disabling Warden::JWTAuth::Middleware and dispatching the token manually. This way I have more control over where and when the token is dispatched (it will be relevant to other things I'm doing).

It could be a nice feature to have on https://github.com/waiting-for-dev/warden-jwt_auth, making the middleware optional and allowing manual trigger for token dispatch and revoke.