Functionalities
Simple log in, log out
First make sure there are no previous cookies in your browser.
Simply log in, and make sure we receive a refresh token.
When we log out, make sure our refresh token array is emptied.
Visit /login with previous refresh token in cookie
First, we have a token (expired or not is ok) in our cookie.
When we look at the cookie on the server side, make sure the jwt token is there.
So whenever we hit /login url, it will execute handleLogin on the server.
We find the user from User Collections, then make sure the passwords match via bcrypt.compare
This tells us if we have a valid user.
If we do, we simply create a new access token and refresh token combo. This is always true, when the user visits /login
Now, the question is, what do we do if we already have a previous refresh token? (embedded in the cookie)
The answer is, we check to see if the previous token exists in the user’s refresh token array. We KEEP any refresh token that does NOT match the previous refresh token.
This is because we don’t want/care about the previous refresh token when we visit /login
We filter it out!
1 2 3 4 |
let existingTokenArray = foundUser.refreshToken; let newRefreshTokenArray = !cookies?.jwt ? existingTokenArray // if there is no previous token, do nothing : existingTokenArray.filter(rt => rt !== cookies.jwt); // there is a previous token, let's filter it out from our user's token array |
If the user has logged in AND there is a jwt embedded in the cookie, we simply clear the cookie. This way, we force the newly logged in user to use the latest created refresh token.
1 |
res.clearCookie('jwt', { httpOnly: true, sameSite: 'None', secure: true }); |
The next step is that we go the normal route. We ADD newly created refresh token to the user’s refresh token array.
Using expired token
* make sure your refresh token is set to 60 seconds.
right before you sign in, there may be expired tokens from previous usage.
Log in (/login) you’ll get an access and refresh token.
Click on the admin link. Look at the refresh token.
In Cookies, click on jwt, and copy the token.
Then click on Home, Wait for 60 seconds for it to expire. Click on the admin link again. This time, verifyJWT on the server side will detect that it has an expired refresh token:
The backend will return a 403 to the client. The client response interceptor gets the 403 and proceeds to do a request for a new refresh token:
It will execute its refresh request to the backend. And later when the backend returns the refresh token, put it in our previous request’s header. Then use Axios’s axiosPrivate to execute the previous request again.
First…let’s look at the refresh request to the backend.
We hit the backend on /refresh, which calls handleRefreshToken. We reference the cookie’s jwt first..keep a handle on it. Then we always clear the client’s cookie jwt.
We look to see if our reference to the cookie’s refresh token exist in any of our users.
Unfortunately, it doesn’t. This we detected a token reuse. So we decode the token to find out which user this refresh token belongs to.
We get the decoded’s username and try to find it in our Users. The reason why is because we want to clear all of this user’s refresh tokens and have them sign in again.
This will make the hacker’s refresh token unusable. And forces our valid user to re-sign in and get a new refresh token to use. And if that’s the case, a 403 will redirect to the /login page again.
Let’s now attempt to use the expired refresh token
Open up Postman,
GET localhost:3500/refresh
Simply paste in the refresh token. Let’s save it and use it by clicking on ‘Send’.
If any of the situation where the user does not exist, or if the username from the decoded token does not match what we have in Users, we return 403 and have them login again.
We get forbidden.