1

JWT Bearer token and stateless REST API question

I am implementing a stateless REST API. I am used to using sessions built into frameworks or 0Auth but now I am in the process of developing my own jwt bearer auth implementation instead of relying of frameworks and services. I have a question about a design decision that I am in the midst of making, so I am hoping some of you auth experts can help me out.

I created a centralized auth server using primarily NodeJS, Express and MongoDB which I intend to authorize access from two separate applications (more in the future) which will share this auth server. I have register, login, route protection in place and working perfectly, however I want to make sure its all glued together right :)

I am wondering if I can do the following to invalidate a users access individually, without changing any secret keys (resetting everyones PW) or hitting the DB to check any DB flags on the user:

  • The user logs in with password username -> auth server returns JWT with 10 min lifetime to the client to be then passed in with each authenticated subsequent request to the resource server, which then checks the validity and parsed the claims in order to provide a new refreshed token in the auth api response auth header.

  • Let’s say a user gets set to inactive due to say fraud - we need access to this account to be removed even though the session is still active as per the valid JWT signature/exp/claim, makes a request and would then be allowed continue as an authorized use unless restrictions are put in place. My point is to avoid storing tokens in the DB, but still be able to instantly invalidate a users request based on the token, without hitting the users collection to check for status active with each validated request.

  • My though is to mimic the refresh token, but instead of sending it as a refreshToken claim in the JWT, overwrite the bearer token which I send in every response if a valid request (checked against auth.user.status === 'active' for instance) is made within 5 min of token timeout and the user input requests a sessions extension. This the token just gets it's life automatically extended and the user never has to log out because it will happen automatically so long as the web client that they are using remains active for the configured period of time. No blacklist of tokens in the DB is needed for this approach either. I plan on implementing a UI component to warn users of an approaching timeout, before refreshing the token. Is there anything wrong with this approach? What’s the point of a refresh token when I can just replace the auth header with an extended lifetime version of itself in the form of a new bearer token (which would be globally saved after each request and sent with each request, repeated etc)? Thanks for helping clear this up for me and anyone else confused by this aspect.

It seems that my options are


  • Use a short lifetime on the token, refresh valid request incrementally each time and warn the user if they are about to expire so they can be issued a new extended exp token lifespan.

Pro: No need to ever check the database strictly for authorization purposes.

Con: Whatever time is left in the tokens expiration from when it expires from the time its invalidated in the database/store - the user remains active and is still able to access until the token expires, even if they get deleted, set to inactive etc.

  • Store all logged out or otherwise invalidated users auth tokens in the dB check a blacklist before permitting access,

  • Check the DB for user.status by the tokens validated sub claim. I


Pro:

Ability to instantly invalidate a user when desired, regardless of the authenticity of the access token provided.

Cons:

Major performance hit by adding database user lookup to the API bearer interceptor middleware, or by possibly even worse managing a token blacklist by inserting invalidated tokens into a lookup.

Wouldn't an auth.user.state during token validation introduce state to the API?

Why are refresh tokens different than regular access tokens? I see refresh tokens typically have much longer lifespan than that if access tokens - but why is that? What am I missing here?

My current plan

Extend the lifetime of the valid authenticated token when a request comes though with each request and overwrite the existing bearer token sent to the client from the auth api with the new short lifespan JWT, then to the client back and to get used again to the resource server? I feel that this way, you could shut any account down within the time of the expiration so I have no choice but to check the user status in the database for each auth request. Since both applications will would make use of the user.status in the same way, I dont see it as a major issue but since this part of the code isn't written I wanted to hear from some of yall!

If the user doesn’t make an authenticated request in n hours or minutes, expire the token... otherwise keep refreshing or ask if the still authenticated user wants to refresh before the token expires.

So just to be safe, I am checking all the claims and also the status (not in the token) against user.auth collection in the DB and also validating the signature of the token.

  • What is your question exactly? How to invalidate JWT tokens? Why do you avoid using a database? What are your performance requirements? – Sjoerd Dec 23 '19 at 13:12
  • 1
    This describes the issues I am also facing. medium.com/@kennch/…, if your want your REST API stateless, you lose the ability to revoke access tokens and must rely on a short expiration since you “trust” the claims of a validated token without looking anything up. If you wish you have immediate control over token revocation then it seems that you must accept the overhead of adding state to the api – Alpha G33k Dec 24 '19 at 0:59
  • 1
    So what is your question about that? – Sjoerd Dec 24 '19 at 7:59
  • @Sjoerd Is there any way of being able to instantly invalidate tokens using the described approach without the overhead of a database lookup with each authorization request to the server? – Alpha G33k Dec 25 '19 at 1:18
  • 1
    I'll put an answer below, but I'm not sure that I completely understand either your question (there are 0 '?'s in your question), or exactly what you are proposing; maybe you could remove some words to make the description simpler? – Mike Ounsworth Dec 28 '19 at 13:43
1

So your goal is to be able to immediately invalidate a user's token, without needing a db. Like you say in comments, this is not really possible.

I'm don't think your design accomplishes this.

My understanding of your design is that instead of recording in the db when a user becomes invalid, you will send them back a token which marks them as invalid, thus preventing them from making subsequent calls (including not being able to refresh their token).

If I'm understanding correctly, when you issue the user an invalid token, they could choose to ignore that token update and continue with their previous token, which would get refreshed, thus allowing an invalid user to prolong their session indefinitely.

That assumes that the client software is behaving correctly and will go ahead and delete the previous cookie.

I think you are only thinking about the success cases and not considering that the client itself could be malicious (either intentionally on the part of the user, or due to malware). What if, for example, the client is logging all their tokens, or the client is going through an HTTP proxy like Burp that logs all HTTP requests and responses?


I'll finish by inventing a quote:

Security that relies on the client to be honest is about as useful as asking the dog to guard the cookies. -- Me

The only way for a server to enforce security is, well, for the server to enforce security. Which means the server needs to know what's going on, or else be able to prove that there is nothing a malicious client could do to circumvent the security. As you say in comments, the only real way to do this is with a DB.

|improve this answer|||||
  • Yeah, I couldn't see a way of doing it without adding state the the user entity or by doing a token blacklist on loggout/lockout etc, thank you for validating my thoughts. I am not understanding how my approach is "trusting" the client in any way, maybe I was unclear of the strategy. Any recommended resources on maintaining a jwt blacklist? Would it be a good approach to run a daily job to delete any tokens from the blacklist as they expire?I know that would self-maintain itself, but I am feeling that there is an easier way than using daily job. Thanks for the help! – Alpha G33k Feb 3 at 16:27
  • Is the added benefit of maintaining a blacklist of invalidated tokens vs setting a user status = inactive better because it removes state from the session? Does this approach fall more in line with true OAUTH? – Alpha G33k Feb 3 at 16:35
  • @AlphaG33k I agree that some mechanism to track the status of JWT tokens is probably needed, but I don't think I have a clear enough picture of your setup to recommend how to optimise that algorithm. – Mike Ounsworth Feb 3 at 16:36
  • In regards to "trusting the client", I'm reacting to this sentence in your question: "Extend the lifetime of the valid authenticated token when a request comes though with each request and overwrite the existing bearer token sent to the client"; That sounds like you're trusting the client to forget the old token value when it receives a new one. Maybe I am misundersntanding your comment? – Mike Ounsworth Feb 3 at 16:39
  • Ahh.. I see. What I mean here is that once the JWT is validated successfully on the API side, each authenticated request responds with a token which auto-extends the life another 15/30 min (refresh with warning appears before session timeout which also refreshes the token using the same criteria). Request token received from API -> Valid in all ways? -> copy (or update if needed) token claims, resign and issue bearer token with extended lifetime of nMinutes. If no authenticated requests come in the specified period of time, show a refresh dialog. If nothing happens auto return to sign on. – Alpha G33k Feb 3 at 16:44

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.