Security & Authentication Skill
Model Selection
Security-critical code requires Sonnet (not Haiku) due to:
-
High risk of vulnerabilities
-
Complex attack vectors
-
Need for thorough validation
NextAuth Configuration
Session Configuration
// Secure session settings
export const authOptions: NextAuthOptions = {
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 days
},
cookies: {
sessionToken: {
name: __Secure-next-auth.session-token,
options: {
httpOnly: true,
sameSite: "lax",
path: "/",
secure: true, // HTTPS only in production
},
},
},
};
Protected Routes
// Server-side protection import { getServerSession } from "next-auth";
export async function GET(request: Request) { const session = await getServerSession(authOptions); if (!session) { return Response.json({ error: "Unauthorized" }, { status: 401 }); } // ... protected logic }
Password Security
Hashing
Always use bcrypt with sufficient rounds:
import bcrypt from "bcrypt";
const SALT_ROUNDS = 12; // Minimum 10, 12 recommended
// Hash password const hashedPassword = await bcrypt.hash(plainPassword, SALT_ROUNDS);
// Verify password const isValid = await bcrypt.compare(plainPassword, hashedPassword);
Password Reset Flow
-
User requests reset (email)
-
Generate secure token (crypto.randomBytes(32) )
-
Store hashed token with expiration (1 hour max)
-
Send reset link via email
-
Validate token on reset page
-
Hash new password, invalidate token
Critical:
-
Never log tokens
-
Rate limit reset requests
-
Invalidate all sessions on password change
OWASP Top 10 Checklist
- Injection
-
Use parameterized queries (Prisma handles this)
-
Validate all user input with Zod
-
Never concatenate SQL strings
- Broken Authentication
-
Strong password requirements
-
Rate limit login attempts
-
Secure session management
-
Password hashing (bcrypt 12+ rounds)
- Sensitive Data Exposure
-
HTTPS everywhere
-
Encrypt sensitive data at rest
-
No secrets in code/logs
-
Secure cookie flags
- XXE (XML External Entities)
-
Disable XML external entity processing
-
Use JSON instead of XML where possible
- Broken Access Control
-
Verify authorization on every request
-
Deny by default
-
Log access control failures
- Security Misconfiguration
-
Remove default credentials
-
Disable unnecessary features
-
Keep dependencies updated
- XSS (Cross-Site Scripting)
-
Never use dangerouslySetInnerHTML
-
Escape user output
-
Content Security Policy headers
- Insecure Deserialization
-
Validate serialized data
-
Use type-safe serialization
- Using Components with Known Vulnerabilities
-
Run npm audit regularly
-
Update dependencies
-
Monitor security advisories
- Insufficient Logging & Monitoring
-
Log security events
-
Monitor for anomalies
-
Alerting on suspicious activity
Rate Limiting
API Route Rate Limiting
import { Ratelimit } from "@upstash/ratelimit"; import { Redis } from "@upstash/redis";
const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(10, "10 s"), });
export async function POST(request: Request) { const ip = request.headers.get("x-forwarded-for") ?? "127.0.0.1"; const { success } = await ratelimit.limit(ip);
if (!success) { return Response.json({ error: "Too many requests" }, { status: 429 }); } // ... handle request }
Sensitive Endpoint Limits
Endpoint Limit Window
Login 5 attempts 15 min
Password reset 3 requests 1 hour
API general 100 requests 1 min
Registration 3 accounts 1 hour
Input Validation
Always validate with Zod:
import { z } from "zod";
const loginSchema = z.object({ email: z.string().email(), password: z.string().min(8).max(128), });
const resetSchema = z.object({ email: z.string().email(), });
const newPasswordSchema = z.object({ token: z.string().min(32), password: z .string() .min(8) .max(128) .regex(/[A-Z]/, "Must contain uppercase") .regex(/[a-z]/, "Must contain lowercase") .regex(/[0-9]/, "Must contain number"), });
Security Headers
// next.config.ts const securityHeaders = [ { key: "X-DNS-Prefetch-Control", value: "on", }, { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload", }, { key: "X-Frame-Options", value: "SAMEORIGIN", }, { key: "X-Content-Type-Options", value: "nosniff", }, { key: "Referrer-Policy", value: "origin-when-cross-origin", }, ];
Security Review Checklist
Before PR
-
No hardcoded secrets
-
All inputs validated
-
Authorization checks in place
-
Sensitive operations rate limited
-
Error messages don't leak info
-
Logs don't contain sensitive data
Critical Code Patterns
Never do:
// BAD - Hardcoded secret const API_KEY = "sk-1234567890";
// BAD - SQL injection risk
const query = SELECT * FROM users WHERE id = ${userId};
// BAD - XSS risk return <div dangerouslySetInnerHTML={{ __html: userInput }} />;
// BAD - Timing attack vulnerability if (token === storedToken) { /* ... */ }
Always do:
// GOOD - Environment variable const API_KEY = process.env.API_KEY;
// GOOD - Parameterized (Prisma) const user = await prisma.user.findUnique({ where: { id: userId } });
// GOOD - Escaped output return <div>{userInput}</div>;
// GOOD - Constant-time comparison import { timingSafeEqual } from "crypto"; if (timingSafeEqual(Buffer.from(token), Buffer.from(storedToken))) { /* ... */ }
Environment Variables
Required for auth:
-
NEXTAUTH_SECRET
-
Random 32+ char string
-
NEXTAUTH_URL
-
Full URL of app
-
RESEND_API_KEY
-
For password reset emails
Never commit:
-
.env files with real values
-
API keys or tokens
-
Database credentials