Enhance Node.js Security: User Logout and Forget Password Guide

User Authentication - Part 3: Logout, Forgot Password & Middleware Magic
Welcome to Part 3 of secure authentication series. In this guide, we'll build an Iron Man–grade logout system and forgot password workflow, and explore how the isLoggedIn
middleware keeps our users protected at every turn.
Let's suit up.
🔐 Part 1: Secure Logout Controller (logOut
)
When a user logs out, we want to:
Remove both access & refresh tokens from cookies
Invalidate the refresh token stored in the DB
Return a success response
✅ Step 1: Find Logged-In User
const loggedinUser = await User.findById(req.user.id);
if (!loggedinUser) {
throw new ApiError(404, "User not found");
}
We extract the user's ID from the decoded JWT payload attached to req.user
.
✅ Step 2: Clear Auth Cookies
const accessTokenCookieOptions = {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
};
res.clearCookie("AccessToken", accessTokenCookieOptions);
const refreshTokenCookieOptions = {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
};
res.clearCookie("RefreshToken", refreshTokenCookieOptions);
Cookies gone = tokens invalidated on client side.
✅ Step 3: Delete Refresh Token from DB
loggedinUser.refreshToken = undefined;
loggedinUser.save();
Prevents reuse of old refresh tokens.
✅ Step 4: Return Response
return res.status(200).json(new ApiResponse(200, "User is loggedOut"));
🚫 Part 2: Forgot Password Controller (forgotPass
)
The user forgot their password? No worries. We'll help them recover it securely.
✅ Step 1: Extract Email from Request
const { email } = req.body;
Pretty standard. Next:
✅ Step 2: Find User by Email
const user = await User.findOne({ email });
if (!user) {
throw new ApiError(404, "User not found");
}
If the email doesn’t exist in the DB, we bounce out.
✅ Step 3: Generate Temp Token
const { token, hashedToken, tokenExpiry } = await user.generateTempToken();
This gives us a plaintext token for email and a hashed one for DB.
✅ Step 4: Save Token to DB
user.forgotPasswordToken = hashedToken;
user.forgotPasswordExpiry = tokenExpiry;
await user.save();
We hash the token before storing it to prevent abuse.
✅ Step 5: Send Email with Reset Link
const resetPassUrl = `${process.env.BASE_URL}/api/v1/users/forgotPass/${token}`;
await sendMail({
email: user.email,
subject: "Reset Password Email",
mailGenContent: resetPasswordEmailContent(user.name, resetPassUrl),
});
Email contains a secure link with the temp token.
🔒 Part 3: Middleware (isLoggedIn
) - The Gatekeeper
This middleware ensures the user has a valid access token, or else tries to refresh it.
✅ Step 1: Check Access Token
const accessToken = req.cookies?.AccessToken;
if (accessToken) {
try {
const decodedData = jwt.verify(accessToken, process.env.ACCESS_TOKEN_SECRET);
req.user = decodedData;
return next();
} catch (err) {
throw new ApiError(404, "Token is invalid");
}
}
If valid, user info is added to req.user
and passed down the chain.
✅ Step 2: Try Refresh Token
const refreshToken = req.cookies?.RefreshToken;
if (!refreshToken) {
throw new ApiError(404, "User is logged out. Please log in again.");
}
If no refresh token, it’s game over.
✅ Step 3: Verify & Issue New Access Token
const decodedRefresh = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
const loggedinUser = await User.findById(decodedRefresh.id);
if (!(loggedinUser.refreshToken == refreshToken)) {
throw new ApiError(400, "Refresh token is fake");
}
const newAccessToken = loggedinUser.generateAccessToken();
res.cookie("AccessToken", newAccessToken, accessTokenCookieOptions);
req.user = jwt.decode(newAccessToken);
User gets a fresh access token if all checks pass.
📊 What’s Next?
Here’s what you should tackle next:
✅ resetPassword Controller — handles setting a new password from token
✅ changePassword Controller — for logged-in users
✅ deleteAccount Controller — delete user + all related data
✅ Protect routes with
isLoggedIn
✅ Add rate limiting to prevent abuse
🔧 Technical Jargon Buster
Access Token: Short-lived token used to authenticate user on each request.
Refresh Token: Longer-lived token used to get a new access token.
JWT: JSON Web Token, stateless authentication token.
httpOnly Cookie: JavaScript can't access it — more secure.
SameSite=lax: Prevents some cross-site CSRF attacks.
ApiError: Custom error class for standardized error responses.
Token Expiry: Token expiration time in milliseconds.
✨ Final Thoughts
You’ve now built a secure logout system and a forgot password workflow that would make Tony Stark proud. Your users are safe, your code is scalable, and your future self will thank you.
Stay tuned for the next part: Reset & Change Password like a pro.
Need the resetPassword
, changePassword
, or deleteAccount
logic? Just say "next part" and I’ll hook you up. ✨
Subscribe to my newsletter
Read articles from Saim Ahmed directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Saim Ahmed
Saim Ahmed
I’m not a prodigy. I didn’t start at 13. I learned the hard way — error by error, late night by late night. I still Google basic stuff, still mess up, still doubt myself. But I show up daily, build real things, and document the process without filters. No polished aesthetics. No fake “10x dev” talk. Just one guy trying to master his craft — publicly, consistently, and without shortcuts. If you relate to the grind more than the glory, welcome to my corner of the internet.