Logout, Deployment & Lessons Learned
Over the previous articles, we built a complete authentication system from scratch.
We implemented:
- Backend architecture using a layered design.
- User registration with secure password hashing using bcrypt.
- Login using JWT Access Tokens and Refresh Tokens.
- Protected routes and authentication middleware.
- Forgot Password and Reset Password workflows using Resend for email delivery.
At this stage, the authentication system is fully functional. However, one important piece is still missing—logging the user out securely.
In this final part, we'll implement the logout flow, deploy the application to production, discuss the challenges encountered during deployment, and summarize the key learnings from the entire project.
Why Logout Matters
Logging out isn't just about redirecting the user back to the login page.
In applications that use JWT authentication, the client may still possess valid authentication tokens. If those tokens remain active after logout, they could potentially be reused until they expire.
A secure logout process ensures that the user's session is completely terminated and prevents the generation of new access tokens.
Logout Flow
The logout process is shown below.
User clicks Logout
│
▼
Frontend sends Logout Request
│
▼
Backend authenticates User
│
▼
Invalidate Refresh Token
│
▼
Return Success Response
│
▼
Frontend clears Authentication State
│
▼
Redirect to Login
Unlike login, logout does not require the user to provide credentials. Instead, the request is authenticated using the existing access token.
Backend Logout Implementation
The logout request first passes through the authentication middleware, which verifies the access token and identifies the currently logged-in user.
Once the user is authenticated, the backend removes the stored refresh token from the database.
By invalidating the refresh token, the application prevents future access token generation, even if the old access token has not yet expired.
const logout = async (req, res) => {
try {
const userId = req.user.id;
const result = await authService.logout(userId);
return res.status(200).json(result);
} catch (err) {
return res.status(400).json({
success: false,
message: err.message,
});
}
};
Repository
The repository performs the database operation required for logout.
Its responsibility is simply to clear the refresh token stored against the authenticated user.
const updatePassword = async (password_hash, id) => {
await pool.query(
`UPDATE users SET reset_token= $1, reset_token_expiry =$2, password_hash =$3 where id= $4`,
[null, null, password_hash, id]
);
};
Keeping this logic inside the repository maintains the same clean architecture used throughout the project.
Frontend Logout
After receiving a successful response from the backend, the frontend clears all authentication-related data.
This includes:
- Access Token
- Refresh Token
- User Information
- Authentication State in Redux
Once the application state is cleared, the user is redirected to the login page.
This ensures that protected routes are no longer accessible.
Deploying the Application
With all authentication features complete, the next step was deploying the application.
I chose the following platforms:
| Application | Platform |
|---|---|
| Frontend | Vercel |
| Backend | Render |
| Database | PostgreSQL |
This setup closely resembles how many real-world applications are deployed.
Backend Deployment (Render)
The backend was deployed to Render.
Deployment required configuring several environment variables, including:
- Database credentials
- JWT secrets
- Resend API key
- Frontend URL
One important lesson I learned is that environment variables are never deployed automatically from the local .env file.
They must be manually added through the Render dashboard.
Frontend Deployment (Vercel)
The React application was deployed using Vercel.
Since this is a Single Page Application (SPA), client-side routing required additional configuration.
Without proper routing configuration, directly visiting URLs such as:
/auth/reset-password/:token
resulted in:
404 NOT FOUND
Configuring Vercel to redirect all requests to index.html resolved the issue and allowed React Router to handle client-side navigation correctly.
Challenges I Faced
Building the authentication system was only part of the journey. Deploying it introduced several real-world challenges.
Some of the issues I encountered included:
SMTP Connection Errors
Initially, I used Nodemailer with Gmail SMTP.
While it worked perfectly in the local environment, the deployed backend on Render consistently failed with IPv6 connection errors.
After investigating the issue, I migrated to Resend, which provided a much simpler and more reliable solution for transactional emails.
Missing Environment Variables
During deployment, the backend repeatedly failed because required environment variables had not been configured on Render.
Although the application worked locally, production deployments require all secrets to be added manually through the hosting platform.
This reinforced the importance of understanding environment management.
Frontend Routing Issues
Password reset links worked locally but returned 404 errors after deployment.
The issue turned out to be related to Vercel's handling of React Router.
Configuring proper SPA rewrites fixed the problem.
CORS Configuration
Since the frontend and backend were deployed on different domains, proper CORS configuration became essential.
Restricting requests to only trusted frontend origins ensured secure communication between the client and server.
Key Learnings
Building this authentication system taught me much more than implementing login and registration.
Some of the biggest takeaways include:
- Designing a scalable backend using layered architecture.
- Understanding how password hashing works internally.
- Implementing stateless authentication using JWT.
- Managing Access Tokens and Refresh Tokens securely.
- Building a complete password recovery workflow.
- Integrating third-party services like Resend.
- Debugging production deployment issues.
- Managing environment variables across different platforms.
- Deploying a full-stack application to production.
More importantly, I learned that building software isn't just about writing code—it's equally about understanding architecture, security, debugging, deployment, and real-world problem solving.
Final Thoughts
Authentication is one of the foundational building blocks of modern web applications.
While libraries and third-party authentication providers make implementation easier, building the entire authentication flow from scratch provides a much deeper understanding of how these systems work internally.
This project helped me understand not only authentication but also backend architecture, API design, database interactions, deployment strategies, and security best practices.
Although there are many possible enhancements—such as email verification, role-based access control, OAuth login, Redis token blacklisting, and multi-factor authentication—the current implementation already provides a strong, production-ready authentication foundation.
I hope this series helps anyone looking to understand authentication beyond simply copying code from tutorials.
Live App: https://auth-flow-five-iota.vercel.app/auth/
Backend API: https://auth-flow-backend-1v2h.onrender.com/
github url: https://github.com/sriyaT/Auth-Flow
Connect : LinkedIn : https://www.linkedin.com/in/t-sriya-b4234510a/, github : https://github.com/sriyaT
Happy Coding! 🚀
Author: Sriya T.


Top comments (0)