# How to Prevent Insecure Code from Cursor

- Tool: Cursor
- Difficulty: Beginner
- Time required: 10-15 min
- Compatibility: Cursor Free+, any web project with authentication
- Last updated: March 2026

## TL;DR

Cursor often generates JWT code that stores tokens in localStorage, uses weak signing algorithms, skips expiration checks, or hardcodes secrets. By adding .cursor/rules/ with JWT security best practices, providing a secure auth utility for Cursor to reference, and prompting with explicit security requirements, you ensure Cursor generates code that handles JWTs safely with httpOnly cookies, RS256 signing, and proper validation.

## Preventing insecure code from Cursor

JWT handling is one of the most security-sensitive patterns in web development, and AI-generated code frequently gets it wrong. Common issues include storing tokens in localStorage (vulnerable to XSS), using HS256 with weak secrets, missing token expiration validation, and hardcoding signing keys. This tutorial configures Cursor to generate secure authentication code by default.

## Before you start

- Cursor installed with a web application project
- Basic understanding of JWT authentication flow
- A signing key or certificate available for RS256
- Familiarity with httpOnly cookies and CSRF protection

## Step-by-step guide

### 1. Create a JWT security rule for Cursor

Add a project rule that specifies secure JWT patterns and explicitly bans common insecure practices. Include both forbidden and required patterns so Cursor has clear boundaries for security-critical code.

```
---
description: Secure JWT handling patterns
globs: "*.ts,*.js,*auth*,*token*,*session*"
alwaysApply: true
---

# JWT Security Rules

## Token Storage:
- NEVER store JWTs in localStorage or sessionStorage (XSS vulnerable)
- ALWAYS use httpOnly, Secure, SameSite=Strict cookies for token storage
- Access tokens: short-lived (15 min), in memory or httpOnly cookie
- Refresh tokens: httpOnly cookie only, longer-lived (7 days)

## Signing:
- NEVER use HS256 with short or hardcoded secrets
- PREFER RS256 or ES256 with key pairs
- NEVER hardcode signing keys in source code
- ALWAYS load keys from environment variables or secret manager

## Validation:
- ALWAYS verify token signature, expiration, issuer, and audience
- ALWAYS check token is not on a revocation/blocklist
- NEVER trust token payload without signature verification
- Handle expired tokens gracefully with refresh flow

## FORBIDDEN:
```typescript
localStorage.setItem('token', jwt);  // NEVER
const secret = 'my-secret-key';       // NEVER hardcode
jwt.decode(token);                    // decode without verify = NEVER
```
```

**Expected result:** Cursor generates secure JWT handling code with httpOnly cookies and proper validation.

### 2. Create a secure auth utility for Cursor to import

Build a secure token management module that Cursor can import instead of generating ad-hoc JWT code. This ensures consistent security patterns across all authentication-related code in your project.

```
import jwt from 'jsonwebtoken';
import { Response } from 'express';

const ACCESS_TOKEN_EXPIRY = '15m';
const REFRESH_TOKEN_EXPIRY = '7d';

export const generateTokens = (userId: string, roles: string[]) => {
  const accessToken = jwt.sign(
    { sub: userId, roles, type: 'access' },
    process.env.JWT_PRIVATE_KEY!,
    { algorithm: 'RS256', expiresIn: ACCESS_TOKEN_EXPIRY, issuer: process.env.JWT_ISSUER }
  );
  const refreshToken = jwt.sign(
    { sub: userId, type: 'refresh' },
    process.env.JWT_PRIVATE_KEY!,
    { algorithm: 'RS256', expiresIn: REFRESH_TOKEN_EXPIRY, issuer: process.env.JWT_ISSUER }
  );
  return { accessToken, refreshToken };
};

export const setTokenCookies = (res: Response, tokens: { accessToken: string; refreshToken: string }) => {
  res.cookie('access_token', tokens.accessToken, {
    httpOnly: true, secure: true, sameSite: 'strict', maxAge: 15 * 60 * 1000,
  });
  res.cookie('refresh_token', tokens.refreshToken, {
    httpOnly: true, secure: true, sameSite: 'strict', path: '/api/auth/refresh', maxAge: 7 * 24 * 60 * 60 * 1000,
  });
};

export const verifyToken = (token: string): jwt.JwtPayload => {
  return jwt.verify(token, process.env.JWT_PUBLIC_KEY!, {
    algorithms: ['RS256'], issuer: process.env.JWT_ISSUER,
  }) as jwt.JwtPayload;
};
```

**Expected result:** A secure auth utility that Cursor imports when generating authentication code.

### 3. Prompt Cursor for a secure login endpoint

When asking Cursor to generate auth endpoints, reference both the security rule and the auth utility. Be explicit about security requirements even if they are in the rules, because double reinforcement improves compliance.

```
@jwt-security.mdc @src/lib/auth-tokens.ts

Create a login endpoint POST /api/auth/login that:
1. Validates email and password from request body with Zod
2. Looks up user by email in the database
3. Verifies password with bcrypt.compare
4. Generates access and refresh tokens using auth-tokens utility
5. Sets tokens as httpOnly, Secure, SameSite cookies (NOT localStorage)
6. Returns only the user profile (no tokens in response body)
7. Handles invalid credentials with a generic error (no user enumeration)
8. Rate limits to 5 attempts per IP per minute
```

> Pro tip: Always say 'NOT localStorage' explicitly in auth prompts. Even with rules, Cursor sometimes falls back to localStorage for token storage because it is the most common pattern in its training data.

**Expected result:** Cursor generates a login endpoint that uses httpOnly cookies, RS256 tokens, and proper security practices.

### 4. Audit existing auth code for security issues

Use Cursor Chat with @codebase to scan your project for insecure JWT patterns. This catches vulnerabilities in both AI-generated and manually written authentication code.

```
@jwt-security.mdc @codebase

Audit this project for JWT security vulnerabilities. Check for:
1. Tokens stored in localStorage or sessionStorage
2. HS256 with short or hardcoded secrets
3. jwt.decode() used without jwt.verify()
4. Missing expiration checks on token validation
5. Tokens returned in response bodies (should be cookies only)
6. Missing httpOnly, Secure, or SameSite cookie flags

For each vulnerability, show the file, the insecure code, and the fix.
```

**Expected result:** Cursor identifies JWT security vulnerabilities across your codebase with specific fixes for each.

### 5. Generate a token refresh flow

Token refresh is critical for security but complex to implement correctly. Prompt Cursor with explicit requirements for refresh token rotation, revocation, and concurrent request handling.

```
@jwt-security.mdc @src/lib/auth-tokens.ts

Create a token refresh endpoint POST /api/auth/refresh that:
1. Reads the refresh token from httpOnly cookie (not request body)
2. Verifies the refresh token signature and expiration
3. Checks the token is not on the revocation blocklist
4. Generates new access AND refresh tokens (rotation)
5. Adds the old refresh token to the blocklist
6. Sets new tokens as httpOnly cookies
7. Handles concurrent refresh requests safely (only one succeeds)
8. Returns 401 if refresh token is invalid or revoked
```

**Expected result:** Cursor generates a secure refresh endpoint with token rotation, revocation checking, and concurrency safety.

## Complete code example

File: `.cursor/rules/jwt-security.mdc`

```markdown
---
description: Secure JWT handling patterns
globs: "*.ts,*.js,*auth*,*token*,*session*"
alwaysApply: true
---

# JWT Security Rules

## Token Storage:
- NEVER store JWTs in localStorage or sessionStorage
- ALWAYS use httpOnly, Secure, SameSite=Strict cookies
- Access tokens: 15 min expiry, in httpOnly cookie
- Refresh tokens: 7 day expiry, httpOnly cookie, restricted path

## Signing:
- ALWAYS use RS256 or ES256 with key pairs
- NEVER hardcode signing keys in source code
- Load keys from environment variables or secret manager
- Support key rotation with kid (Key ID) header

## Validation (ALWAYS):
- Verify signature with public key
- Check expiration (exp claim)
- Verify issuer (iss claim)
- Verify audience (aud claim)
- Check revocation blocklist

## FORBIDDEN Patterns:
```typescript
localStorage.setItem('token', jwt);        // XSS vulnerable
const secret = 'my-secret-key';             // Hardcoded secret
jwt.decode(token);                          // No verification
jwt.verify(token, secret, { algorithms: ['HS256'] }); // Weak algo
res.json({ token: accessToken });           // Token in response body
```

## Required Pattern:
```typescript
import { generateTokens, setTokenCookies, verifyToken } from '@/lib/auth-tokens';
const tokens = generateTokens(user.id, user.roles);
setTokenCookies(res, tokens);
res.json({ user: { id: user.id, email: user.email } });
```
```

## Common mistakes

- **Cursor stores JWT in localStorage by default** — localStorage.setItem is the most common JWT storage pattern in tutorials and training data. Cursor defaults to it unless explicitly told otherwise. Fix: Add NEVER store JWTs in localStorage to your rules and always say NOT localStorage in auth prompts. Double reinforcement is essential for security patterns.
- **Using HS256 with a short string secret** — HS256 with a simple string is the easiest JWT pattern to implement, so Cursor defaults to it. It is vulnerable to brute-force attacks if the secret is weak. Fix: Specify RS256 in your rules and provide the auth utility that uses RS256. Remove any HS256 examples from your codebase.
- **Returning tokens in JSON response body** — Cursor generates API responses with tokens in the body because that is the pattern in most REST API tutorials. This exposes tokens to JavaScript and XSS attacks. Fix: Add to rules: NEVER return tokens in response body. ALWAYS set tokens as httpOnly cookies. Return only user profile data in the response.

## Best practices

- Store JWTs in httpOnly, Secure, SameSite cookies — never localStorage
- Use RS256 or ES256 with key pairs instead of HS256 with shared secrets
- Set short access token expiry (15 minutes) with refresh token rotation
- Always verify signature, expiration, issuer, and audience on every request
- Implement a token revocation blocklist for logout and security events
- Create a shared auth utility file so Cursor imports it consistently
- Audit auth code with @codebase regularly since security issues are the most critical to catch

## Frequently asked questions

### Is localStorage ever acceptable for JWT storage?

Only if your application has no XSS risk at all, which is effectively impossible for web apps. HttpOnly cookies are always the safer choice because JavaScript cannot access them.

### What about storing tokens in memory for SPAs?

In-memory storage (React state or a module-level variable) is acceptable for access tokens in SPAs. The token is lost on page refresh, which is fine if you have a refresh token in an httpOnly cookie.

### Should I use sessions instead of JWTs?

Server-side sessions with session IDs in httpOnly cookies are often simpler and more secure than JWTs. JWTs are better for stateless microservices. Consider your architecture before choosing.

### How do I handle CSRF with httpOnly cookies?

Use SameSite=Strict cookies and add a CSRF token for state-changing requests. Include these requirements in your Cursor rules for auth endpoints.

### Can Cursor generate OAuth2 flows?

Yes, but OAuth2 is complex. Provide your OAuth provider's documentation via @docs and reference existing auth utilities. Review generated OAuth code carefully since mistakes can expose user accounts.

### Can RapidDev help secure our authentication system?

Yes. RapidDev conducts security reviews of authentication systems and helps teams implement secure JWT handling, OAuth2 flows, and session management with properly configured Cursor rules.

---

Source: https://www.rapidevelopers.com/cursor-tutorial/how-to-keep-cursor-ai-from-suggesting-insecure-jwt-handling-methods
© RapidDev — https://www.rapidevelopers.com/cursor-tutorial/how-to-keep-cursor-ai-from-suggesting-insecure-jwt-handling-methods
