All articles are first published on my site.
I wrote a blog post two years ago. It started like this:
After long research, I finally got an imp...
For further actions, you may consider blocking this person and/or reporting abuse
It might be a silly question, but why do we need a refresh token? Since the client can easily have access to the refresh token and use it to get an access token, why don't we just extend the access token to 8 hours or so and be done with it. I am new to this. So maybe I am missing something
Not a stupid question at all. You could use only an access token, But the problem is that in the middleware, you verify only if the access token is valid. You do not verify the user. So, in case a user might be deleted, if he keeps his access token, he will still get access to the app for a maximum of 8h ( in case you deleted him right after a new token was given ). If you keep the access token lifetime short (15 minutes), when a request to refresh token will be made, it will check again the user in the db, and based on that it will provide a new token or not. So, for ex, if you deleted the user, he will still have access for a maximum of 15 minutes. As a tldr, this depends on the purpose of your app. If you don’t mind a user getting access to your app for a longer time after you want to restrict it, you can just provide an access token. If there are still other questions, just ask and I will try to clarify😬
Aha yes, it makes sense now. :)
Ok, one more question for my understanding. Now, the access token has been sent by the client (a React app for example) and it was found to be expired. Who will initiate the request for a new token using the refresh token? will the user on the React app be requested to login again? This part confuses me. If the user will not be asked to login again (which I assume this is the case), how to make the experience smooth for the user so he will not know what's happening in the backend and keeps using the app while the refresh token is still alive?
You are right. He will not be asked to login again. There are multiple aproaches, but the one I use is usually with axios interceptors. The general flow is the following: You just make normal requests, and if the backend sends you an error that the access token is expired, you intercept that error and make a call to refreshToken in order to receive a new access + refresh token. If that call responds with 200, then you just continue making calls with the new token. Otherwise, if the refresh token request failed, it probably means also the refresh token is invalid, so you redirect the user to /login again.
I will try to make a blog post on the frontend part for this blog, but probably next week. Do you prefer any framework?
You are amazing man :)
Well, my app is using Node, Express and Prisma for backend .. and React for the frontend. Axios is something I use also for sending API requests from my React app. Honestly, I have never used or knew about "Interceptors". Would be great if you have a blog post which adds to this one so we can learn the follow end-to-end 😬
Done. You can check it now :D
I was wondering what would be the best way to verify the user in the middleware. If a user is deleted but still has access to the app (even for max. 15 mins) each request might produce some chaos (Except you verify the userId on each request). How would you solve this without producing a too big impact on the Peformance?
Is it enough to check if an user exists based on the userId provided by the signed jwt? This should be safe, right? (Because you can only generate a valid signed jwt using the secret that the backend holds) Maybe the middleware could cache users like this:
Would love to get your Feedback about this!
I think the JWT auth should be stateless ( the access token part ), so I would make the checks that you said on the refresh token endpoint. Regarding the “delete” problem, i would make the lifetime for the access token probably 1 minute. And it will achive what you are trying to do basically. But instead of checking the user every request (either in cache or db ), we check it only on refresh token ( every minute, if we set the expiry for access token to 1min )
There is a tradeoff (how many times to query the db agains having it valid more time and maybe stale), and the expire time should be based on your app logic. Regarding the “chaos” part, probably if the actions require him to write data, in the db you will have a foreign key to that user_id on the table he is writing to. And Because the user will no longer exist, there will be a db error.
Just perfect, I loved this implementation!! Applied it to my project
Awesome! What are you building? 💙
A simple CRUD just to study
Great article, well done 👏👏
Great articles, thank you for sharing!
Absolutely lovin' it
Glad you liked it😬
Thanks for this, Totally inspired .
But I'm having a problem with the typescript version (from your repo), the server is not running I'm getting "Property 'user' does not exist on type 'Request>"
from the middleware.ts file on the requireUser function, looks like user is not present on the request.
I've tried to created .d.ts
`import { z } from "zod";
export { };
declare global {
namespace Express {
interface Request {
user: z.AnyZodObject; // 👈️ turn off type checking
}
}
}`
but still, no luck, app crashed
How would do to get passed this, as I real like your approach. Thanks
github.com/mihaiandrei97/authentic...
Here is an example
Thanks for the reply (a quick one) :),
But I do still have trouble,
line 67 & 77 in middlewares.ts
req.user = payload
{ const user = req.user }
the error is "
TSError: ⨯ Unable to compile TypeScript:
src/middlewares.ts:67:9 - error TS2339: Property 'user' does not exist on type 'Request>'.
67 req.user = payload
~~~~
src/middlewares.ts:77:22 - error TS2339: Property 'user' does not exist on type 'Request>'.
github.com/mihaiandrei97/authentic...
You also need to have this in tsconfig:
"typeRoots": ["./typings"],
My tscofig.json
"baseUrl": "src",
"paths": {
"@/config/*": ["config/*"],
"@/utils/*": ["utils/*"],
"@/assets/*": ["assets/*"]
},
// "typeRoots": ["./typings", "./node_modules/@types"],
"typeRoots": ["./typings"],
"types": ["express", "@types/jest"],
"resolveJsonModule": true,
I even changed the file structure to honor the tsconfig, restarted the TS server (on VSCode) but n luck,. Looks like req.user is of type Any not of ParsedToken! Every where the req.user is of type Any, that's why I can't even assign req.user to payload.
I don't know what I'm missing or doing wrong
I'm not using the exact same process but I took heavy inspiration from this article and my method is the same. I was just wondering, from a security standpoint, is there any problem with the server auto-generating a new access and refresh token when needed and only sending a 401 when there is a problem generating tokens or the refresh token has expired? IMO this would be better since the client would only need one request instead of two (the second request is to
/refreshToken
) but I don't know much about auth so would love some feedback for this!I'm not really sure about auto-generating them, however, if you want to not make 2 requests, what I implemented was on frontend, you can also decode the jwt token, and check the expiration every X seconds, and if it will expire in the next 30s, you can call /refreshToken. This way, you automatically refresh the tokens without the backend explicitly telling you it is expired.
Do you have a plan to move this into Typescript?
You have an example on how to do it in TypeScript here:
github.com/mihaiandrei97/authentic...
thanks
how do we handle logout logic
You just delete the tokens from the frontend (ex: from localstorage )
thanks very much. I have one more question. Whenever I throw new Error() , my server crash. How do we prevent it?
github.com/mihaiandrei97/express-p...
You need to have the errorHandler defined and imported in the main file as middleware
Can you explain the
delete user.password;
in '/profile'?Also, it would be nice look this code in TypeScript.
Hello! That is in order to not return the password in the response. Here you can find the typescript equivalent: github.com/mihaiandrei97/authentic...
Oh thanks a lot! I'm gonna check your repo for future references. Keep going!
what about xss attacks? if the refresh token is stored on the client. Wouldn't it be better to store it on the server, protecting the user? For example, store it for 7 days and if the client does not have a refresh token, then if the response is Unauthorized, it will be possible to request a new pair of access and refresh tokens. Next, when the client receives a response in the form of a new access token, repeat the request that was originally made. It turns out 3 requests, but it’s safer, refresh is stored on the server. What do you think?
Thank you very much for this, only one thing:
npx create-express-api -d auth-server
instead:
npx create-express-api auth-server
On the middlewares.js you are using "payload" as property or method with "req" but I can't access to it. How can I use it? Am I missing something?
You need to use the IsAuthenticated middleware on a controller in order to have access to a payload inside controllers
see this youtube.com/watch?v=YcH2kxqK3nc