Opinionated rules for designing, coding and maintaining secure, modern authentication & authorization flows with Node.js back-ends and React/Next.js front-ends using TypeScript, OAuth 2.1, Supabase Auth or Clerk.
Building secure authentication isn't just about checking if a user is logged in anymore. With sophisticated attacks targeting session hijacking, credential stuffing, and privilege escalation, your traditional auth patterns are leaving massive security gaps.
Most developers are still building authentication like it's 2015:
The result? You're spending weeks building auth systems that sophisticated attackers bypass in minutes.
These Cursor Rules implement a comprehensive Zero-Trust authentication and authorization system for TypeScript applications. Instead of building auth from scratch or patching security holes reactively, you get battle-tested patterns that assume breach and verify everything.
What you get:
Stop spending sprints retrofitting MFA, session management, and privilege controls. These rules scaffold secure patterns from day one.
// Instead of building custom session validation
if (!isLoggedIn(user)) return redirect('/login');
// You get comprehensive context validation
const authResult = await requireAuth(ctx, {
mfaRequired: true,
maxSessionAge: 900, // 15 minutes
requiredScope: ['account:write']
});
Pre-built integration patterns for Clerk, Supabase Auth, and OAuth 2.1 providers eliminate the research and experimentation phase.
// Clerk integration ready in minutes, not days
export const GET = requireAuth(async (ctx) => {
const account = await getAccount(ctx.user.id);
return NextResponse.json(account);
});
TypeScript schemas and validation patterns prevent auth vulnerabilities from reaching production.
const TokenSchema = z.object({
sub: z.string().uuid(),
exp: z.number().int().positive(),
scope: z.array(z.string()),
});
// Invalid tokens fail fast with clear error messages
Before: Manually checking user factors, building custom TOTP validation, managing backup codes
// Fragile, incomplete MFA check
if (!user.totpEnabled || !validateTOTP(code)) {
throw new Error('MFA required');
}
After: Declarative MFA requirements with adaptive escalation
// Comprehensive MFA with risk-based escalation
const requireMFA = createAuthMiddleware({
factors: [AuthFactor.TOTP, AuthFactor.WEBAUTHN],
adaptiveRisk: true,
gracePeriod: 300 // 5 minutes
});
Before: Permanent admin roles or complex role switching systems
// Dangerous: permanent elevated access
if (user.role === 'admin') {
// Admin can do anything, forever
}
After: Time-boxed privilege elevation with automatic revocation
// Temporary admin access with audit trail
await issueTempRole({
userId: ctx.user.id,
role: 'ADMIN_TEMP',
ttlSeconds: 600, // 10 minutes only
justification: 'User data export request'
});
Before: Custom OAuth flow implementation with security gaps
// Manual OAuth with security pitfalls
const authUrl = `${OAUTH_URL}?client_id=${CLIENT_ID}&redirect_uri=${CALLBACK}`;
// Missing PKCE, state validation, proper error handling
After: Security-hardened OAuth 2.1 with PKCE and PAR
// OAuth 2.1 with all security features enabled
const authFlow = createOAuthFlow({
provider: 'google',
pkce: true,
par: true, // Pushed Authorization Requests
requireSignedRequests: true
});
npm install zod @clerk/nextjs @supabase/supabase-js
Add the rules to your .cursorrules file and configure your environment schema:
// env-schema.ts
import { z } from 'zod';
export const envSchema = z.object({
CLERK_SECRET_KEY: z.string().min(1),
SUPABASE_SERVICE_ROLE_KEY: z.string().min(1),
// Validation prevents runtime auth failures
});
auth/
├── adapters/ # clerk.ts, supabase.ts
├── middleware/ # require-auth.ts
├── policies/ # rbac.ts, jit.ts
├── routes/ # oauth-callback.ts
├── schemas/ # login.ts, token.ts
└── tests/
// middleware/require-auth.ts
export const requireAuth = (options: AuthOptions) =>
async (ctx: RequestContext) => {
const authResult = await validateAuthContext(ctx, options);
if (authResult.isErr()) {
return handleAuthError(authResult.error);
}
return authResult.value;
};
For Supabase:
// Enable short-lived JWTs
const supabase = createClient(url, key, {
auth: {
persistSession: true,
detectSessionInUrl: true,
flowType: 'pkce' // Required for security
}
});
For Clerk:
// Enable origin restrictions
export const clerkMiddleware = () => {
return withClerkMiddleware({
limitOrigins: ['https://yourdomain.com'],
publishableKey: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
});
};
After implementing these rules, a mid-size e-commerce platform saw:
Unlike generic auth tutorials or basic authentication libraries, these rules encode the collective wisdom of security engineering teams at scale. Every pattern addresses real attack vectors and operational challenges that emerge only after handling millions of authentication events.
The rules don't just prevent vulnerabilities - they make secure patterns easier to implement than insecure ones. When the path of least resistance is also the most secure path, your entire team builds better auth systems by default.
Ready to stop playing security catch-up? Implement these rules and start building authentication systems that assume breach and verify everything. Your users' security and your development velocity will both thank you.
You are an expert in Node.js, TypeScript, React/Next.js, OAuth 2.1, Supabase Auth, and Clerk.
Key Principles
- Adopt a strict Zero-Trust model: always verify identity, device and context on every request.
- Default-deny: never grant access without an explicit, validated allow-list rule.
- MFA everywhere: require at least two factors (TOTP + biometric/web-authn) for privileged actions and high-risk logins.
- Passwordless first: prefer magic-link or WebAuthn to traditional passwords; store no passwords unless legacy support is unavoidable.
- Just-In-Time (JIT) & Least Privilege: issue scoped, time-boxed tokens (≤15 min) and temporary RBAC roles.
- Continuous authentication: refresh proof of presence on sensitive workflows (e.g., money transfer) with WebAuthn assertions.
- Auditability: emit immutable, structured logs (JSON, RFC 5424) for every authentication, authorization, and policy decision.
- Security > convenience: never trade off cryptographic guarantees for UX; instead, layer adaptive MFA.
TypeScript (Node.js & React)
- Always use strict mode (`"strict": true`) and `esnext` targets; disallow `any` except in migration shims.
- Prefer `interface` for external contracts (DTOs, token payloads) and `type` for compositions.
- Function signatures: `(ctx: AuthContext, deps: Deps) => Promise<Result<T, AuthError>>` to enforce explicit context + error handling.
- Use `enum` only for finite, stable sets (e.g., `enum AuthFactor { PASSWORD, TOTP, WEBAUTHN }`).
- Folder layout (all lowercase, dash-separated):
|- auth/
|- adapters/ (clerk.ts, supabase.ts)
|- middleware/ (require-auth.ts)
|- policies/ (rbac.ts, jit.ts)
|- routes/ (oauth-callback.ts)
|- schemas/ (login.ts, token.ts)
|- tests/
- Never store secrets in code; load via `process.env` and validate with `env-schema.ts` (using `zod`).
- Example: validating a JWT payload
```ts
import { z } from 'zod';
const TokenSchema = z.object({
sub: z.string().uuid(),
exp: z.number().int().positive(),
scope: z.array(z.string()),
});
export type TokenPayload = z.infer<typeof TokenSchema>;
```
Error Handling & Validation
- Validate all external input at module boundaries with Zod; reject before business logic.
- Return typed `Result` objects, never throw inside domain code; only throw at HTTP edge to propagate proper status.
- Normalize auth errors into the following codes: `INVALID_CREDENTIALS`, `MFA_REQUIRED`, `TOKEN_EXPIRED`, `INSUFFICIENT_SCOPE`, `UNVERIFIED_EMAIL`.
- Early-exit pattern:
```ts
if (!ctx.user) return Err(new AuthError('INVALID_CREDENTIALS'));
```
- Instrument failures with `context.logger.warn({ err, ip: ctx.ip })` for SIEM ingestion.
Framework-Specific Rules
Next.js (App Router)
- Put all mutating actions behind `POST` handlers in `app/api/**/route.ts` and protect with `requireAuth()` middleware.
- Use `cookies().get('sb-access-token')` for Supabase session, re-validate on every request.
- Server components must never call `window` or read local storage. Delegate to client component with `useAuth()` hook from Clerk/Supabase.
Supabase Auth
- Enable `jwt.exp` ≤ 900 s and `jwt.allow_refresh: true`.
- Configure `audience: 'authenticated'` and issue service-role keys only in secure server runtimes.
- Map Postgres RLS policies to `user_id = auth.uid()`; never disable RLS.
- Rotate `anon/service` keys monthly; automate via GitHub Actions secret store.
Clerk
- Use `publishableKey` client-side, `secretKey` server-side; never expose `secretKey`.
- Activate `incremental static regeneration` by adding `import { auth } from '@clerk/nextjs';` only in server actions.
- Enforce `clerk.limitOrigins` to your exact domain list.
OAuth 2.1
- Use PKCE + state for public clients; disallow implicit flow.
- Always request `offline_access` separately; store refresh tokens encrypted with AES-GCM & rotated KEKs.
- Use PAR (Pushed Authorization Requests) and `require_signed_request_object` where supported.
- Token introspection and revocation endpoints must be protected with mTLS.
Additional Sections
Testing
- Unit: Mock external IdPs with `openid-client` stub servers.
- Integration: Spin up Supabase local stack via `docker-compose`; run migrations + RLS policies.
- Security: Write Jest tests to assert RLS denies unintended data leakage.
- E2E: Use Playwright to simulate MFA flows with WebAuthn virtual authenticator.
Performance
- Cache JWK sets from external IdPs for 10 minutes (respect `Cache-Control: max-age`).
- Use lazy loading for heavy auth SDKs on the client (e.g., Clerk) via dynamic import.
Security
- Enable CSP with `script-src 'self' 'sha256-...'` allowing only Clerk/Supabase domains.
- Set `SameSite=Lax` on auth cookies, `Secure`, `HttpOnly`, `Path=/`.
- Log anomalous events to a dedicated queue; trigger adaptive MFA when risk signals (TOR exit node, impossible travel) are detected.
- Run weekly dependency scans (`npm audit --production`) and dependency-track SBOM in CI.
Examples
1. Protecting an API route in Next.js
```ts
// app/api/account/route.ts
import { NextResponse } from 'next/server';
import { requireAuth } from '@/auth/middleware/require-auth';
import { getAccount } from '@/data/account';
export const GET = requireAuth(async (ctx) => {
const account = await getAccount(ctx.user.id);
return NextResponse.json(account);
});
```
2. Just-In-Time role elevation
```ts
import { issueTempRole } from '@/auth/policies/jit';
await issueTempRole({
userId: ctx.user.id,
role: 'ADMIN_TEMP',
ttlSeconds: 600, // 10 minutes
});
```
Common Pitfalls
- Treating refresh tokens as "forever" keys – always implement revocation & rotation.
- Forgetting to verify `nonce` in OpenID ID tokens.
- Mixing auth libraries (e.g., NextAuth + Clerk) leading to multiple cookies; standardize on one provider.
- Using `localStorage` for tokens – use httpOnly cookies instead.
Refer to OWASP ASVS v4.0 sections 2 & 3 for deeper requirements.