I'm using the following authentication scheme for my current MERN project. I'd like to know if it's secure, particularly the renewal approach (more details after the description).
User signs in over HTTPS. (Passwords are stored hashed and salted with BCrypt alongside the BCrypt-generated salt.) Server verifies the credentials and creates a JWT token. The token is signed with a 512-byte key that was generated via crypto random and is stored in the server's environment variables. It contains only one claim (other than the standard iss, exp, and nbf claims), which is the user ID, an integer. It expires in 1 hour. The token is sent to the client using the Bearer scheme in the Authorization header on the response.
Whenever the client receives a JWT in the Authorization header of any authenticated response, it replaces its current token with the one given and stores it in session storage. Every authenticated request then includes that token with the Bearer scheme in the request's Authorization header. The server verifies that the token is valid, signed correctly, not expired, etc. If any validation here fails, it returns a 401 Not Authorized response.
When the server receives a valid authenticated request with a token that expires within the next 10 minutes, a new token is generated with the expiration pushed back by another hour, then signed and sent in the response's Authorization header for the client to use.
If a token is revoked for any reason, including sign-out, it's dropped into a MongoDB database containing all revoked tokens. Whenever the server is validating a token, if it exists in this database, it's rejected with a 401 same as if it were just expired.
My concern is that this renewal scheme means authentication timeouts can be anywhere from 10 minutes to 1 hour from the last request. The fact that it's not a single consistent time makes me wonder if this method isn't good and may be introducing a vulnerability that I don't know about.
Is this scheme robust? If not, what kinds of things do I need to consider that it doesn't cover?