How to Secure JWT Refresh Tokens in Vibe-Coded Apps
OverMCP Team
Quick answer
To secure JWT refresh tokens in vibe-coded apps, you must store them in httpOnly cookies, implement rotation and reuse detection, and set short expiration times. If you're building with AI tools, you likely have refresh tokens stored in localStorage or without rotation—both are critical vulnerabilities that can lead to account takeover. This guide walks you through the exact steps to fix these issues, even if you didn't write the code yourself.
What to check first
Before you start fixing, audit your current implementation. Most vibe-coded apps skip security by default, so you'll likely find at least one of these issues:
localStorage or sessionStorage? If yes, they're exposed to XSS attacks.Use a free AI app security scanner to quickly identify these issues and others like leaked secrets or misconfigurations.
Step-by-step fix
1. Move refresh tokens to httpOnly cookies
The most secure way to store refresh tokens is in an httpOnly cookie. This prevents JavaScript from accessing them, mitigating XSS attacks. Here's how to set it in a Next.js API route:
// app/api/auth/refresh/route.ts
import { serialize } from 'cookie';
export async function POST(request: Request) {
const body = await request.json();
// Verify the refresh token (e.g., with jose or jsonwebtoken)
const user = await verifyRefreshToken(body.refreshToken);
if (!user) {
return new Response('Unauthorized', { status: 401 });
}
// Generate new tokens
const accessToken = generateAccessToken(user);
const newRefreshToken = generateRefreshToken(user);
// Set refresh token as httpOnly cookie
const cookie = serialize('refresh_token', newRefreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
path: '/',
maxAge: 60 * 60 * 24 * 7 // 7 days
});
return new Response(JSON.stringify({ accessToken }), {
headers: { 'Set-Cookie': cookie }
});
}Note the httpOnly: true and sameSite: 'strict' options. For more on security headers, check our security headers checker.
2. Implement token rotation and reuse detection
Token rotation means every time you use a refresh token, you issue a new one. This limits the window of opportunity for a stolen token. Reuse detection means if an old (already rotated) token is presented, you revoke the entire session. Here's a simple implementation using a database:
-- Track refresh tokens with a version and revoked flag
CREATE TABLE refresh_tokens (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
token_hash TEXT NOT NULL,
version INT DEFAULT 1,
revoked BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT now()
);In your refresh endpoint:
// Verify token hash from cookie
const { user, tokenRecord } = await verifyRefreshTokenFromCookie();
if (!tokenRecord || tokenRecord.revoked) {
return new Response('Unauthorized', { status: 401 });
}
// Check if token is already used (reuse detection)
if (tokenRecord.used_at) {
// Revoke all tokens for this user
await revokeAllUserTokens(user.id);
return new Response('Session revoked', { status: 401 });
}
// Mark current token as used
await markTokenUsed(tokenRecord.id);
// Issue new refresh token and update version
const newToken = generateRefreshToken(user);
await storeToken(user.id, newToken, tokenRecord.version + 1);This ensures that if an attacker and the legitimate user both try to use the same token, only one succeeds and the session is terminated.
3. Set short expiration times
Access tokens should expire in 10-15 minutes. Refresh tokens can be longer, but not indefinite. 7 days is a good starting point. If your app needs more, consider implementing absolute timeout or sliding expiration.
4. Use HTTPS everywhere
This is non-negotiable. Cookies with secure: true only work over HTTPS. If you're on Vercel, you get HTTPS by default. Run our SSL certificate checker to verify.
5. Monitor for token leaks
Use a secret leak scanner to ensure you haven't accidentally committed refresh token secrets to GitHub. Also, check your client-side code for any hardcoded secrets.
Common mistakes
Vibe-coded apps often get these wrong:
sameSite prevents CSRF attacks on your auth endpoints. Use strict or lax.How OverMCP can help
OverMCP is a continuous security monitoring platform designed for vibe-coded apps. It scans your code, dependencies, and configuration to catch vulnerabilities like the ones above before attackers do. Connect your Vercel project and get real-time alerts on security issues. Start with a free AI app security scanner to see where you stand.
FAQ
Why is localStorage insecure for refresh tokens?
LocalStorage is accessible by any JavaScript running on your domain. If an attacker can inject a script (XSS), they can read the token and send it to their server. httpOnly cookies prevent JavaScript access, so the token is safe even if XSS exists.
How often should I rotate refresh tokens?
Rotate on every refresh—each time you issue a new access token, also issue a new refresh token. This ensures that a stolen token becomes useless after the next use.
What should I do if I suspect a refresh token leak?
Immediately revoke all refresh tokens for the affected user. Implement reuse detection to automatically revoke sessions when a token is used twice. Also, rotate any secrets that might be compromised. Run a secret leak scanner to check your codebase.
---
Secure your vibe-coded app today. OverMCP provides automated security scanning for AI-built applications. Start free at [overmcp.com](https://www.overmcp.com/).