Security best practices for web applications. Covers OWASP Top 10, authentication, authorization, input validation, CSP, and secure headers.
Implement comprehensive security practices to protect web applications from common vulnerabilities.
| Risk | Prevention | |------|------------| | Injection | Parameterized queries, input validation | | Broken Auth | MFA, secure sessions, rate limiting | | Sensitive Data | Encryption at rest/transit, minimal exposure | | XXE | Disable external entities, use JSON | | Broken Access | RBAC, verify ownership | | Misconfig | Security headers, disable debug | | XSS | Output encoding, CSP | | Insecure Deserialization | Validate input, use safe formats | | Vulnerable Components | Audit deps, update regularly | | Logging Gaps | Log security events, monitor |
// next.config.js
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: 'X-XSS-Protection',
value: '1; mode=block'
},
{
key: 'Referrer-Policy',
value: 'strict-origin-when-cross-origin'
},
{
key: 'Permissions-Policy',
value: 'camera=(), microphone=(), geolocation=()'
},
{
key: 'Content-Security-Policy',
value: `
default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data: https:;
font-src 'self';
connect-src 'self' https://api.frankx.ai;
frame-ancestors 'none';
`.replace(/\n/g, '')
}
];
module.exports = {
async headers() {
return [
{
source: '/:path*',
headers: securityHeaders,
},
];
},
};
import { z } from 'zod';
const UserInputSchema = z.object({
email: z.string().email().max(255),
name: z.string().min(1).max(100).regex(/^[a-zA-Z\s]+$/),
age: z.number().int().min(0).max(150),
url: z.string().url().optional(),
});
// Sanitize HTML content
import DOMPurify from 'isomorphic-dompurify';
const sanitizedHtml = DOMPurify.sanitize(userInput, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],
ALLOWED_ATTR: ['href'],
});
// GOOD: Parameterized queries
const user = await prisma.user.findUnique({
where: { email: userEmail }
});
// BAD: String interpolation - NEVER do this
// const user = db.query(`SELECT * FROM users WHERE email = '${email}'`);
import { hash, verify } from 'argon2';
// Hash password
const hashedPassword = await hash(password, {
type: 2, // Argon2id
memoryCost: 65536,
timeCost: 3,
parallelism: 4,
});
// Verify password
const isValid = await verify(hashedPassword, password);
// lib/session.ts
import { cookies } from 'next/headers';
import { SignJWT, jwtVerify } from 'jose';
const secret = new TextEncoder().encode(process.env.SESSION_SECRET);
export async function createSession(userId: string) {
const token = await new SignJWT({ userId })
.setProtectedHeader({ alg: 'HS256' })
.setIssuedAt()
.setExpirationTime('7d')
.sign(secret);
(await cookies()).set('session', token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7, // 7 days
path: '/',
});
}
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(5, '1 m'), // 5 attempts per minute
analytics: true,
});
export async function loginRateLimit(ip: string) {
const { success, remaining } = await ratelimit.limit(`login:${ip}`);
if (!success) {
throw new Error('Too many login attempts. Try again later.');
}
return remaining;
}
// lib/permissions.ts
type Role = 'admin' | 'editor' | 'viewer';
type Permission = 'read' | 'write' | 'delete' | 'admin';
const rolePermissions: Record<Role, Permission[]> = {
admin: ['read', 'write', 'delete', 'admin'],
editor: ['read', 'write'],
viewer: ['read'],
};
export function hasPermission(role: Role, permission: Permission): boolean {
return rolePermissions[role]?.includes(permission) ?? false;
}
// Middleware usage
export async function requirePermission(permission: Permission) {
const session = await getSession();
if (!session || !hasPermission(session.role, permission)) {
throw new Error('Forbidden');
}
}
// For forms in Next.js, use Server Actions which have built-in CSRF protection
// For API routes, use the double-submit cookie pattern:
import { cookies } from 'next/headers';
import { nanoid } from 'nanoid';
export async function generateCsrfToken() {
const token = nanoid(32);
(await cookies()).set('csrf', token, {
httpOnly: true,
secure: true,
sameSite: 'strict',
});
return token;
}
export async function verifyCsrfToken(submittedToken: string) {
const storedToken = (await cookies()).get('csrf')?.value;
if (!storedToken || storedToken !== submittedToken) {
throw new Error('Invalid CSRF token');
}
}
// NEVER commit secrets to git
// Use environment variables
// .env.local (gitignored)
DATABASE_URL=postgresql://...
JWT_SECRET=super-long-random-string
API_KEY=sk-...
// Access in code
const apiKey = process.env.API_KEY;
// Validate required secrets at startup
const requiredEnvVars = ['DATABASE_URL', 'JWT_SECRET'];
for (const envVar of requiredEnvVars) {
if (!process.env[envVar]) {
throw new Error(`Missing required environment variable: ${envVar}`);
}
}
npm audit)Avoid these dangerous practices:
Best practices:
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer