#Introduction: The Vulnerability Landscape
Full-stack applications built on Node.js and Next.js are the backbone of modern web startups, but standard tutorials frequently teach unsafe shortcuts: storing raw JWT tokens in browser localStorage, skipping CORS whitelisting, and trusting unvalidated client JSON payloads.
In this technical breakdown, we walk through our agency's production security protocol for hardening web APIs.
#1. Dual-Token Authentication with Silent Refresh
Storing JWTs in localStorage leaves your application completely vulnerable to Cross-Site Scripting (XSS). If an attacker injects a malicious third-party script, they can read the token and impersonate the user indefinitely.
Our architecture uses a split-token approach:
- Access Token: Short-lived (15 minutes), stored solely in memory (React context/state).
- Refresh Token: Long-lived (7 days), stored in an
httpOnly,secure,sameSite: 'strict'cookie.
// Secure Refresh Cookie Configuration
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
path: '/api/auth/refresh', // Scoped exclusively to refresh route
});#2. Distributed Sliding-Window Rate Limiting
To protect against brute-force credential stuffing and API abuse, we implement a sliding-window counter backed by Redis:
import { Redis } from 'ioredis';
const redis = new Redis(process.env.REDIS_URL!);
export async function checkRateLimit(ip: string, limit = 60, windowSeconds = 60): Promise<boolean> {
const currentTimestamp = Date.now();
const windowStart = currentTimestamp - (windowSeconds * 1000);
const key = `ratelimit:${ip}`;
const multi = redis.multi();
multi.zremrangebyscore(key, 0, windowStart);
multi.zadd(key, currentTimestamp, `${currentTimestamp}:${Math.random()}`);
multi.zcard(key);
multi.expire(key, windowSeconds);
const results = await multi.exec();
const requestCount = results?.[2]?.[1] as number;
return requestCount <= limit;
}#3. Zod-Powered Runtime Ingestion Guardrails
Every incoming API payload must be strictly validated before touching your business logic or database. Any unexpected keys must be stripped immediately.
// Runtime payload validation
export async function validatePayload<T>(schema: z.ZodSchema<T>, data: unknown): Promise<T> {
const result = schema.safeParse(data);
if (!result.success) {
throw new ValidationError(result.error.flatten());
}
return result.data;
}#Summary Checklist
- [x] Zero JWTs stored in
localStorage. - [x] Refresh tokens restricted to
httpOnlycookies withSameSite=Strict. - [x] Sliding-window rate limiters deployed on all authentication endpoints.
- [x] Strict Zod schemas sanitizing all external JSON payloads.
Ankit Bhuria
Founder, Upgrader Boy • National SIH 2024 Winner
We design and ship high-performance MERN & Next.js 15 platforms, IoT telematics architectures, and real-time distributed backends for ambitious startups and enterprises.