Back to Articles
Backend Architecture 2026-07-20 12 min read

Building a High-Performance MERN Authentication System with JWT, Refresh Tokens & RBAC

Sameer Khan

Sameer Khan

Full Stack Developer & Software Engineer

Node.js Express.js JWT React Security MongoDB

Building a High-Performance MERN Authentication System with JWT, Refresh Tokens & RBAC

Stateless authentication is the backbone of modern web applications. In high-concurrency microservices and full-stack MERN (MongoDB, Express, React, Node.js) architectures, naive JWT implementations—such as storing access tokens in localStorage—expose applications to Cross-Site Scripting (XSS) vulnerabilities.

In this deep dive, we will walk through engineering a production-ready, enterprise-grade authentication system featuring HTTP-Only SameSite cookies, short-lived Access Tokens, sliding Refresh Token rotation, and declarative Role-Based Access Control (RBAC).

---

1. Security Threat Model & Token Architecture

Before writing code, let's understand the two primary attack vectors in modern web authentication:

1. **Cross-Site Scripting (XSS):** If an attacker injects a malicious script via an unsafe input, the script can access localStorage and exfiltrate authentication tokens instantly. 2. **Cross-Site Request Forgery (CSRF):** If credentials are stored in cookies, browsers automatically attach them to cross-origin requests unless strict cookie attributes (SameSite=Strict or SameSite=Lax with HttpOnly and Secure) are configured.

The Dual-Token Security Model

To mitigate both vectors, we implement a dual-token strategy:

- **Access Token:** Short lifespan (15 minutes). Signed using HMAC SHA-256 (HS256) or RSA SHA-256 (RS256). Transmitted via Authorization header or memory state. - **Refresh Token:** Longer lifespan (7 days). Stored in a cryptographically random, HTTP-Only, Secure, SameSite Cookie. Stored in Redis or MongoDB with hashed signatures to support instant revocation.

---

2. Express.js Authentication & Refresh Middleware Implementation

Let's inspect the core Node.js middleware for verifying short-lived JWT access tokens and handling sliding session refreshes.

typescript
import { Request, Response, NextFunction } from 'express';

export interface AuthenticatedRequest extends Request { user?: { id: string; email: string; roles: string[]; }; }

export const authenticateToken = ( req: AuthenticatedRequest, res: Response, next: NextFunction ) => { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1];

if (!token) { return res.status(401).json({ error: 'Access token required' }); }

jwt.verify(token, process.env.ACCESS_TOKEN_SECRET!, (err, user) => { if (err) { return res.status(403).json({ error: 'Invalid or expired access token' }); } req.user = user as AuthenticatedRequest['user']; next(); }); }; ```

Role-Based Access Control (RBAC) Guard

typescript
export const authorizeRoles = (...allowedRoles: string[]) => {
  return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
    if (!req.user || !req.user.roles) {
      return res.status(403).json({ error: 'Access denied: No role permissions attached' });

const hasPermission = req.user.roles.some((role) => allowedRoles.includes(role));

if (!hasPermission) { return res.status(403).json({ error: 'Access denied: Insufficient privileges' }); }

next(); }; }; ```

---

3. Database Schema & Token Revocation with MongoDB

When a user logs out or changes their password, we must invalidate all existing refresh tokens. Here is the Mongoose user schema with refresh token tracking:

typescript

const UserSchema = new Schema({ name: { type: String, required: true }, email: { type: String, required: true, unique: true, index: true }, passwordHash: { type: String, required: true }, roles: { type: [String], default: ['USER'] }, refreshTokenHash: { type: String, default: null }, tokenVersion: { type: Number, default: 0 }, }, { timestamps: true });

export const UserModel = mongoose.model('User', UserSchema); ```

---

4. Performance & Key Takeaways

1. **Zero XSS Token Exposure:** Storing access tokens in memory and refresh tokens in HTTP-only cookies guarantees that malicious third-party scripts cannot extract credentials. 2. **Sub-10ms Token Refresh:** Using Redis or indexed MongoDB queries for token revocation checks ensures negligible authentication overhead. 3. **Seamless UX:** Axios silent token refreshing guarantees users stay logged in without unexpected modal prompts or page reloads.

*Engineered by Sameer Khan — Full Stack Developer & Software Engineer.*

Related Engineering Articles