Security Skill
Comprehensive security practices based on OWASP Top 10 and industry standards.
When to Use This Skill
Auto-invoke when:
- •Implementing authentication/authorization
- •Handling user input or form data
- •Storing or processing sensitive data
- •Building API endpoints
- •Working with databases
- •Implementing file uploads
- •Code reviews for security issues
- •Configuring CORS or CSP
Critical Security Patterns
Pattern 1: Authentication & Authorization
When: Protecting routes and resources
Good:
// ✅ Proper auth check
export async function GET(req: Request) {
const session = await getSession(req)
if (!session) {
return new Response('Unauthorized', { status: 401 })
}
const data = await db.user.findUnique({
where: { id: session.userId }
})
return Response.json(data)
}
// ✅ Role-based access
export async function DELETE(req: Request, { params }) {
const session = await getSession(req)
if (!session) {
return new Response('Unauthorized', { status: 401 })
}
if (session.role !== 'admin') {
return new Response('Forbidden', { status: 403 })
}
await db.user.delete({ where: { id: params.id } })
return new Response(null, { status: 204 })
}
Bad:
// ❌ No auth check
export async function GET(req: Request) {
const userId = req.headers.get('user-id')
const data = await db.user.findUnique({ where: { id: userId } })
return Response.json(data) // Returns ANY user's data!
}
Why: Always verify authentication before accessing protected resources. Check authorization (role/ownership) before operations.
Pattern 2: Password Security
When: Storing and verifying passwords
Good:
// ✅ Hash passwords with bcrypt
import bcrypt from 'bcryptjs'
const hashedPassword = await bcrypt.hash(password, 12)
await db.user.create({
data: {
email,
password: hashedPassword,
}
})
// Verify password
const isValid = await bcrypt.compare(inputPassword, user.password)
Bad:
// ❌ NEVER store plain text passwords
await db.user.create({
data: {
email,
password: password, // NEVER DO THIS
}
})
Why: Passwords must be hashed with bcrypt/argon2 (minimum 12 rounds). Never store plain text.
Pattern 3: SQL Injection Prevention
When: Executing database queries
Good:
// ✅ Use parameterized queries
const userId = req.query.id
await db.user.findUnique({
where: { id: userId } // ORM handles escaping
})
// ✅ Or with raw SQL, use parameters
await db.$queryRaw`SELECT * FROM users WHERE id = ${userId}`
Bad:
// ❌ VULNERABLE: String concatenation
const userId = req.query.id
const query = `SELECT * FROM users WHERE id = ${userId}`
await db.execute(query) // SQL Injection risk!
Why: Never concatenate user input into SQL. Use ORMs or parameterized queries.
Pattern 4: Input Validation
When: Handling user input
Good:
// ✅ Validate all input
import { z } from 'zod'
const userSchema = z.object({
email: z.string().email(),
password: z.string().min(12),
age: z.number().int().min(18)
})
export async function POST(request: Request) {
const body = await request.json()
const result = userSchema.safeParse(body)
if (!result.success) {
return NextResponse.json(
{ error: result.error.format() },
{ status: 400 }
)
}
const user = await db.user.create({ data: result.data })
return NextResponse.json(user, { status: 201 })
}
Bad:
// ❌ No validation
export async function POST(request: Request) {
const body = await request.json()
const user = await db.user.create({ data: body })
return NextResponse.json(user)
}
Why: Always validate and sanitize user input. Never trust client data.
Pattern 5: CORS Configuration
When: Setting up API access
Good:
// ✅ Restrictive CORS
app.use(cors({
origin: ['https://yourapp.com', 'https://admin.yourapp.com'],
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
}))
Bad:
// ❌ Allow all origins with credentials
app.use(cors({
origin: '*',
credentials: true
}))
Why: Restrictive CORS prevents unauthorized cross-origin requests.
Pattern 6: Error Handling
When: Returning error messages
Good:
// ✅ Generic errors in production
app.get('/api/user/:id', async (req, res) => {
try {
const user = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id])
res.json(user)
} catch (error) {
console.error('Database error:', error) // Log server-side
res.status(500).json({ error: 'Internal server error' })
}
})
Bad:
// ❌ Exposes implementation details
app.get('/api/user/:id', async (req, res) => {
try {
const user = await db.query('SELECT * FROM users WHERE id = $1', [req.params.id])
res.json(user)
} catch (error) {
res.status(500).json({ error: error.message, stack: error.stack })
}
})
Why: Never expose stack traces or internal details in production.
Code Examples
Example 1: Secure Authentication Endpoint
import bcrypt from 'bcryptjs';
import { z } from 'zod';
const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(12),
});
export async function POST(request: Request) {
const body = await request.json();
const result = loginSchema.safeParse(body);
if (!result.success) {
return new Response('Invalid input', { status: 400 });
}
const user = await db.user.findUnique({
where: { email: result.data.email }
});
if (!user || !(await bcrypt.compare(result.data.password, user.password))) {
return new Response('Invalid credentials', { status: 401 });
}
const session = await createSession(user.id);
return NextResponse.json({ session });
}
Example 2: Input Validation with Zod
const userSchema = z.object({
name: z.string().min(2).max(100),
email: z.string().email(),
age: z.number().int().min(18).max(120),
role: z.enum(['USER', 'ADMIN']),
});
export async function POST(request: Request) {
const body = await request.json();
const result = userSchema.safeParse(body);
if (!result.success) {
return NextResponse.json(
{ error: result.error.format() },
{ status: 400 }
);
}
const user = await db.user.create({ data: result.data });
return NextResponse.json(user, { status: 201 });
}
For comprehensive examples and detailed implementations, see the references/ folder.
Quick Security Checklist
Before deploying:
- • All secrets in environment variables (not code)
- • Passwords hashed with bcrypt/argon2 (rounds ≥ 12)
- • Input validation on all user inputs
- • Parameterized queries (no string concatenation in SQL)
- • HTTPS enforced
- • CORS configured restrictively
- • Security headers configured (CSP, HSTS, etc.)
- • Rate limiting on auth endpoints
- • Logging for security events
- • Dependencies updated (npm audit clean)
- • Authentication on all protected routes
- • Authorization checks before operations
- • Error messages don't leak information
Progressive Disclosure
For detailed information, see:
- •OWASP Top 10 Details - Complete OWASP Top 10 analysis with examples
- •Authentication Patterns - JWT, sessions, MFA, input validation
References
This skill covers security fundamentals. Always stay updated with latest OWASP guidelines and security best practices.