Securing the Stack: Role-Based Authentication and Custom JWT Verification
Best practices for implementing secure user authentication in Node/Express applications using JWTs, HTTP-only cookies, and client side route guards.
Best practices for implementing secure user authentication in Node/Express applications using JWTs, HTTP-only cookies, and client side route guards.
Authentication is one of the most critical aspects of web development. In my blood donation platform, LifeDrop, failing to secure backend endpoints could mean exposing private donor records.
Here is how I architected a role-based authentication flow in Express and React.
Storing JWTs in localStorage or sessionStorage is a major security risk. It makes your tokens vulnerable to Cross-Site Scripting (XSS) attacks. If an attacker injects a malicious script, they can easily read the token.
The solution: HTTP-only Cookies.
These cookies cannot be accessed via JavaScript (document.cookie returns empty), protecting them from XSS.
res.cookie('token', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 3600000 // 1 hour
});Instead of verifying roles inside every controller, write a generic authorization middleware. This keeps your route definitions readable and safe:
const verifyRole = (allowedRoles) => {
return (req, res, next) => {
if (!req.user || !allowedRoles.includes(req.user.role)) {
return res.status(403).json({ message: 'Forbidden: Insufficient privileges' });
}
next();
};
};
// Usage
router.get('/admin/dashboard', verifyToken, verifyRole(['Admin']), getAdminData);On the React frontend, we must protect pages from unauthorized navigation. Using React Router or Next.js middleware, check user roles stored in global state. If a normal donor tries to access the /admin route, redirect them to a fallback dashboard.
Structuring authentication this way keeps your application dry, modular, and highly secure.
Showaib Hossain
Mathematics Honors & MERN Developer