There are many posts about JWT but none have seemed to solve my confusion.
In my current project (Next.js 14 and Postgres) I am attempting to create my own authentication to learn more about important concepts.
Currently, when a user logs in, an access token gets stored in the cookies (HTTP only). I have middleware that checks for the validity of this token, but when this token is not valid or expired, I want my middleware to call an api to retrieve a new access token with a refresh token.
Do I store this refresh token in the cookies also or is it possible to store in my postgres db?
Because if I want my middleware to call an api to get a new access token, I grab the refresh token and check the validity of it then get a new access token.
I don't know how to implement it with the postgres db because I don't understand how the server can know what users' refresh token to grab from the table.
So really my question is, is it okay just to store the refresh token with the access token? If not, how can I implement the database logic, because I have heard it's good to have the refresh token stored in the server.
I think your question is a bit unclear what is executed client side and what is executed server side. I will make some assumptions.
First of all, the client must store both your access token and your refresh token, and both must be sotred securely on the client side. See https://stackoverflow.com/a/76889949/2889165
You have a few different options for how to store the refresh token client side. I have seen the following:
- Store it as a field in the access token JWT. This has the disadvantage that when the JWT is expired, most libraries will treat the JWT as "bad" and not differentiate it from a JWT that has incorrect signing (manipulated, or malicious). Also, if JWT is stolen, so is the refresh token.
- Store the refresh token in another cookie for the same domain. This has the disadvantage that it's sent in every request but it's easy to implement. Also, if JWT is stolen, it's likely that the refresh token gets stolen too.
- Store the refresh token in a cookie for a specific auth-sub-domain. This has the disadvantage that it makes the setup more complex.
- Store the refresh token in a safe Session Storage on the client. This is probably the best way I have seen, I think this is what Auth0 does behind the scenes.
Secondly the refresh token can either be self sustained, like being a signed JWT used in a stateless backend, or the refresh token can be stored both client side and server side for a stateful backend.
- In the fist case, the Refresh token (JWT) is enough for the backend to take a decision if the user can refresh the access token (another JWT) or not. This is however not much different compared to just having a regular JWT with a longer lifetime, not much security gained.
- In the second case, the backend compares the refresh token with the token stored in the backend (in your case perhaps in the Postgres DB. If there is a match, the JWT can be renewed. This has the advantage that you can:
- Delete the server side refresh token if the user logs out, user changes password, backend detects some malicious behaviour etc.
- Ensure that a JWT can only be refreshed once, so that if a malicious actor stole a copy of the JWT, the user session will not continue without noticing that the JWT was stolen. Only the end user or the hacker will manage to renew the JWT. If there is a second request to renew the same JWT (using an old refresh token), you can delete the refresh token altogether preventing anyone to renew their JWT once again. Not you have detected a suspected activity and will enforce the user to login again.