Best practices to invalidate JWT while changing passwords and logout in node.js? [closed]
Asked Answered
E

5

86

I would like to know the best practices to invalidate JWT without hitting db while changing password/logout.

I have the idea below to handle above 2 cases by hitting the user database.

1.Incase of password changes, I check for password(hashed) stored in the user db.

2.Incase of logout, I save last-logout time in user db, hence by comparing the token created time and logout time, I can able to invalidate this case.

But these 2 cases comes at the cost of hitting user db everytime when the user hits the api. Any best practise is appreciated.

UPDATE: I dont think we can able to invalidate JWT without hitting db. So I came up with a solution. I have posted my answer, if you have any concern, you are welcome.

Expellee answered 27/2, 2015 at 7:20 Comment(1)
You can't do this. Don't use JWTs if you need revocation. Generally, don't use JWTs as a replacement for sessions. That is not their intended purpose, and they don't make a good replacement for sessions. See developer.okta.com/blog/2017/08/17/…Centaurus
E
96

When No Refresh token is used:

1.While changing password: when the user changes his password, note the change password time in the user db, so when the change password time is greater than the token creation time, then token is not valid. Hence the remaining session will get logged out soon.

2.When User logs out: When the user logs out, save the token in a seperate DB (say: InvalidTokenDB and remove the token from Db when token expires). Hence user logs out from the respective device, his sessions in other device left undisturbed.

Hence while invalidating a JWT, I follow the below steps:

  1. Check whether the token is valid or not.
  2. If valid, check it is present in invalidTokenDB (a database where logged out tokens are stored till their expiry time).
  3. If its not present, then check the token created time and changed password time in user db.
  4. If changed password time < token created time, then token is valid.

Concern with the above method:

  1. For each api request, I need to follow all the above steps, which might affect performance.

When Refresh token is used: with expiry of access token as 1 day, refresh token as lifetime validity

1. While changing password: When the user changes his password, change the refresh token of the user. Hence the remaining session will get logged out soon.

2. When User logs out: When the user logs out, save the token in a seperate DB (say: InvalidTokenDB and remove the token from Db when token expires). Hence user logs out from the respective device, his sessions in other device left undisturbed.

Hence while invalidating a JWT, I follow the below steps:

  1. check whether the token is valid or not
  2. If valid, check whether the token is present in InvalidTokenDB.
  3. If not present, check the refresh token with the refresh token in userDB.
  4. If equals, then its a valid token

Concern with the above method:

  1. For each api request, I need to follow all the above steps, which might affect performance.
  2. How do I invalidate the refresh token, as refresh token has no validity, if its used by hacker, still the authentication is valid one, request will be success always.

Note: Although Hanz suggested a way to secure refresh token in Using Refesh Token in Token-based Authentication is secured? , I couldn't able to understand what he is saying. Any help is appreciated.

So If anyone have nice suggestion, your comments are welcome.

UPDATE: I am adding the answer incase your app needs no refresh token with lifetime expiry. This answer was given by Sudhanshu (https://stackoverflow.com/users/4062630/sudhanshu-gaur). Thanks Sudhanshu. So I believe this is the best way to do this,

When No Refresh token needed and no expiry of access tokens:

when user login, create a login token in his user database with no expiry time.

Hence while invalidating a JWT, follow the below steps,

  1. retrieve the user info and Check whether the token is in his User database. If so allow.
  2. When user logs out, remove only this token from his user database.
  3. When user changes his password, remove all tokens from his user database and ask him to login again.

So with this approach, you don't need to store neither logout tokens in database until their expiry nor storing token creation time while changing password which was needed in the above cases. However I believe this approach only valids if your app has requirements with no refresh token needed and no expiry of the tokens.

If anyone has concern with this approach, please let me know. Your comments are welcome :)

Expellee answered 2/3, 2015 at 6:46 Comment(9)
I came up with same approach as yours man, but you should also add expiry time on change passwod field see my answer below :)Cotten
and instead of normal database you can use redis as it is in memory cache so look up time will be very lessCotten
if the token created time is before the changed password time, shouldn't the token be invalid?Tourbillion
@amiawizard may I know which scenario you're talking about? I believe I have answered the question , " when the user changes his password, note the change password time in the user db, so when the change password time is greater than the token creation time, then token is not valid. Hence the remaining session will get logged out soon."Expellee
Doesn't looking in a database/datastore defeat the purpose of JWT?Cabman
I would do it just a little bit different: Store every token you create into the database. Now when a user changes his/her password, look up the token in the database and put it into an 'invalid token list'. This list can than be stored into database or (much better) into a local redis cache. Fill the redis cache on startup and add on database update. When it is okay that a logout happens after a few minutes, you can have a static sync which checks the database every 5 minutes whats the last update date is.Verticillaster
@TheQuantumPhysicist just adding the token to some kind of black-list DB on password change or log out will do the trick. The answer describes this as an option. Instead of criticizing, maybe try to elaborate a little more. In the end, you have two options: either invalidate refresh tokens or invalidate access tokens. Both options require database access. The stateless/self-contained nature of JWTs makes it impossible to invalidate them on demand. That's sad many people advise to use tokens but forget to mention what it takes if you want features like logout for example.Guaco
You can as well just add a column if you are using RDBMS, add a column to your users' table like access_token and when the user logs out just clear this token out. When the user logs in update the column with a newly generated token.Guaco
JWT is just a token format and primary reason to use it is to exchange information (claims). I like to think of it as some kind of identity card with some expiration date.Guaco
P
19

There is no way I know of to arbitrarily invalidate a token without involving a database one way or another.

Be careful with Approach 2 if your service can be accessed on several devices. Consider the following scenario...

  • User signs in with iPad, Token 1 issued and stored.
  • User signs in on website. Token 2 issued. User logs out.
  • User tries to use iPad, Token 1 was issued before user logged out from website, Token 1 now considered invalid.

You might want to look at the idea of refresh tokens although these require database storage too.

Also see here for a good SO discussion regarding a similar problem, particular IanB's solution which would save some db calls.

Proposed solution Personally, this is how I'd approach it...user authenticates, issued with access token with a short expiry (say 15 mins) and a refresh token valid either for a much longer period or indefinitely. Store a record of this refresh token in a db.

Whenever the user is 'active', issue a new auth token each time (valid for 15 mins each time). If the user is not active for over 15 minutes and then makes a request (so uses an expired jwt), check the validity of the refresh token. If it's valid (including db check) then issue a new auth token.

If a user 'logs out' either on a device or through a website then destroy both access refresh tokens client side and importantly revoke the validity of the refresh token used. If a user changes their password on any device, then revoke all their refresh tokens forcing them to log in again as soon as their access token expires. This does leave a 'window of uncertainty' but that's unavoidable without hitting a db every time.

Using this approach also opens up the possibility of users being able to 'revoke' access to specific devices if required as seen with many major web apps.

Pleiades answered 27/2, 2015 at 8:35 Comment(13)
Appreciate your feedback on second approach. IanB's solution provides good practise while changing password, but I still not getting the logic when the user logs out. As you explained, when the user logs out, he has to log out only in the current system, how can I achieve that?Expellee
@gopinathshiva See new suggested solution above. This limits db hits but should provide the functionality you require.Pleiades
When the user logs out , how do I destroy all existing tokens on client side? Also if I do, then it will logs out on all devices. But still, those tokens are in valid state. If a hacker uses that token still authentication will be a valid one (assume if token is valid for 1 week). This is not what I need. I would like to log out the user only on respective device, but also token should be securedExpellee
I agree with your answer on revoking the refresh token while changing passwords. But if I revoke refresh token while user logs out, then it will logs out on all devices and user have to log in againExpellee
I have posted my solution below, gave an update to the question, and I also have the respective concerns for my suggested answer. Your comments are welcome.Expellee
@gopinathshiva When I say 'destroy existing tokens' on logout, I mean remove any record of access/refresh tokens from storage on that particular device - yes the access token would still be valid if someone had access to it. In my solution I suggest using short-lived access tokens to limit this 'window' of opportunity (which always exists). I would suggest the solution you have provided is really losing the point of jwts altogether.Pleiades
@gopinathshiva Regarding your point about revoking refresh tokens forcing login...I probably wasn't clear that you should issue (and record) a different refresh token for each device. So when a user logs out on a particular device, invalidate that particular refresh token. If a user changes password etc, invalidate all that user's refresh tokens.Pleiades
Do we have many refresh token for a single user. I thought a user could have many access token and a single refresh token. Correct me If I'm wrongExpellee
'I would suggest the solution you have provided is really losing the point of jwts altogether.' - I am not getting what you mean here, could you pls explain which solution you are talking about. I wouldnot like to a short-lived access token, that's why I suggested this solutionExpellee
'Regarding your point about revoking refresh tokens forcing login...I probably wasn't clear that you should issue (and record) a different refresh token for each device' - I think you are getting it wrong, I provided the solution considering a user can have a single refresh token and many access token.Expellee
No offence but I don't think I'm getting it wrong - refresh tokens can be tied to a user or user/device combination. Auth0 for example say 'Refresh tokens can be issued and revoked for each combination of app, user and device.'Pleiades
@Pleiades I have to say, I really like this solution. Our application is currently hitting the database every request to check session validity, and it's not even in production but I'm already concerned about it. Thanks for posting this.Hanaper
Do we need a refresh token for this ? I mean we can just set the expiration time to 5 minutes and within these 5 minutes we do not check if the token data (username, email passwordHash (hashed again for the token to prevent leak)) changed. After the 5 minutes we simply check if the passwordHashHash in the token matches the hash of the passwordhash in the database (among other data that we might want to validate) and either generate a new token with a new expiration time or respond with 403 UnauthorizedGerome
D
11

I am not sure if I'm missing something here but I find that the accepted answer is more complicated than is necessary.

I see that db has to be hit to validate or invalidate a token for each api request, however the total process could have been simpler as I see things here.

Whenever a jwt is created, i.e. during login or change/reset password, insert the jwt with userid into a table and maintain a jti (a uuid number basically) for each jwt. The same jti goes into jwt payload too. Effectively jti uniquely identifies a jwt. A user can have multiple jwts at the same time when the account is accessed from multiple devices or browsers in which case, jti differentiates the device or the user-agent.

So the table schema would be, jti | userId. (and a primary key ofcourse)

For each api, check if the jti is in the table, which means the jwt is a valid one.

When the user changes or resets the password, delete all the jti of that userId from the db. Create and insert a new jwt with a new jti into the table. This will invalidate all the sessions from all other devices and browsers except the one that changed or reset the password.

When the user logsout, delete that particular jti of that user but not all. There would be a Single Login but not a single Logout. So when the user logs out, he shouldnt be logged out from all the devices. However, deleting all the jtis would logout from all the devices too.

So it would be one table and no date comparisons. Also it would be the same case if a refresh token is used or not.

However to minimize the db interference, and possible delays, cache usage would certainly help to ease things on processing time front.

Note: Please reason if you are down voting it.

Dariusdarjeeling answered 6/12, 2016 at 6:34 Comment(4)
I don't want to check the database all the time when using jwt. In your case i have to. I think it is much cheaper to check if a token is invalid as this is not the common case. And you can make a token even invalid with a delay (like 5 minutes) instead of validity: It has to be valid asap.Verticillaster
@Verticillaster I didn't understand on how do you decide on when to invalidate the jwts of the user from all devices. I did have the thought of reissuing a jwt with 3 secs to invalidate it the moment it is created, but I couldn't figure out on how would I know which jwt to invalidateDariusdarjeeling
When you create a JWT, you store it in the database (which is okay because it only happens when logging in). The JWT has than an expiration date which is checked everytime. Additional to that, you check if it is on the blacklist (that could be a database table OR in reddis). When a user changes his/her password, you look up all JWTs from this user and check all which are still valid and put them onto your blacklist. Advantage: This blacklist is much smaller and can be kept in memory easily. Also it is okay for the blacklist to be out of sync/a few minutes behind.Verticillaster
Feel like the whole point of JWT is redundant if you have to check a database for every API call. Might as well use sessions.Briton
O
2

If a user is changing their password, you're going to hit the db there. But don't want to hit the db for authorization?

I have found the benefits of storing a per user string, and a global shared string hashed together gives us the most flexibility with our JWT implementation. In this particular case I'd store a hash of the password to use with the global string and hash them together for a JWT secret.

Obrien answered 30/8, 2016 at 15:4 Comment(0)
C
-3

I agree solely with @gopinath answer just want to add one thing that you should also remove the change password time when all of your tokens expired for example suppose you have set 3 day expiry time for every token to expire now instead of just normaly saving change password time in database you can also set its expiry time of 3 days because as obviously tokens before this will be expired so no need to check for every token again that whether its expiry time is greater then change password time or not

Cotten answered 11/1, 2016 at 17:32 Comment(21)
Cool appreciate your answer. I have a query, Sorry if am incorrect. Say if you're not storing changed password time in database, then login would happen with the tokens created with old password also right . Example you have logged in using mobile, now changed your password in computer, but still session runs in mobile for the 3 days. I believe in this case, session should not work in mobile. Because of this case only, I added a logic of storing changed password time in database.Expellee
Ok i got now where was the problem actually in my case what was happening was that i was using jsonwebtoken for node.js now in that module it is predefined that when you login for tokens which are expired (before 3 days) it will automatically tell you that the token is expired so now i don't have to check in my change_pasword_field in db for them to check that are they expired or not got me ??Cotten
I got your answer but the question which I told you is different. You mentioned that the module will take care of expired tokens. I agree which it should. But here the scenario is, lets say I have logged into application on Jan13 using my password in MOBILE (old password), Now I changed the application password on Jan14 in PC. So by now all previous tokens generated using my old password should not work.Expellee
Now if I didn't store, changed password time in my database, I could not able to logout the tokens generated with old password. Lets say in the above example, the token generated on Jan13 still will work for the next 3 days(ie., till Jan16 ,if token expiry is set to 3 days). Do you got me now?Expellee
actually i just misthought something yea u are absolutely right i have to store change_password field and should not set expiry time on it, actually can you please tell me one thing are you using both mobile app or only website ?? also if you are using mobile then what is the expiry time you are setting ??Cotten
because the problem i am having is that mobile apps never ask for login again they set no expiry time now how are you solving this issue because if there is no expiry time then i have to keep all my logout tokens in my database which i think in future will require lot of database size and i cannot handle this amount of data in redis ??Cotten
are you there man, waiting for your reply ??Cotten
Hi mate, I believe my logic work in both case (web & mobile app), actually according to my logic, on every http request, u got to check the token expiry and also change password logic. If you do that, then I believe you won't get into any token issue. Are you getting me?Expellee
yea but mobile apps never ask for reenter user credentials again (point being is for them this expiry time is infinite) so how are you dealing with that please tell me ??Cotten
you can set expiry time anything you want. But once password is changed, you have to logout them in all other devices. This could be done only by checking changed password time and token creation time. So on every http request from your mobile app, check the last change password time and token creation time. I believe this would work and I am not sure this is the efficient way to do this.Expellee
actually u don't get it, i wanna ask is as in mobile apps i have to set expiry time to lifetime so now i have to store all logout tokens in my database for lifetime OK now because of which i thought i should store all login tokens in my database and when a request comes i will check whether that token is inside that user column in my database (Actually my point being was instead of saving all logout token for lifetime(because they will become huge in number) why not store only currently login tokens of the user and once he logout then remove it from my database)Cotten
please tell me if i am thinking it in wrong way or you think that i can do something better it will be really appreciable because i have my own startup and i am implementing this token thing right now so it will be a really important help for me THANK YOU :)Cotten
"why not store only currently login tokens of the user and once he logout then remove it from my database" => Please understand this wont work if he changes password. Imagine your account is hacked when you use the app in net centre. Now you're changing app password in your own computer. As per your solution, still hacker can use your app as token lifetime is set to lifetime. So I believe this never be the ideal solution for many apps. Agree?Expellee
"i have to store all logout tokens in my database for lifetime OK ... Actually my point being was instead of saving all logout token for lifetime(because they will become huge in number" => Store logout token in database and remove it from the database once the token expires. Refer docs.mongodb.org/manual/tutorial/expire-data . I believe same case might exist for different database.Expellee
My best wishes for your startup :)Expellee
one thing u miss when user changes his password what i will do is i will remove all login tokens except that one that has change the password associated with that user from my database so your first query is solved i guess now for the second query yeah you are removing them when they expire, but as the expiry time is lifetime so they will not get removed man don't u think again they will be huge in number please tell me if i am missing anything ??Cotten
Yeah agree with your answer , my answer was wrong with tokens set to lifetime expiry. I believe your way of approaching thing is good. I believe in your case there is no refresh token. So you can go with your approach :) I believe your approach holds good for lifetime expiry :)Expellee
thanks a lot man for helping me out of this man :)Cotten
Hi Sudhanshu, I have added your comment as an answer in my update. Please check and edit if needed :)Expellee
No need for edit, also thanks for adding, this hope it will be helpful for someone regarding this problem :)Cotten
cool . Sure it would help someone :)Expellee

© 2022 - 2024 — McMap. All rights reserved.