Security Hardening
Protect your application against the OWASP Top 10, implement input validation, manage secrets properly, and build security into your development workflow.
The OWASP Top 10 and why it matters
The OWASP Top 10 is a list of the most critical web application security risks, updated periodically by security experts. These are not theoretical threats — they are the actual vulnerabilities exploited in real breaches. Understanding them is the minimum security knowledge every developer needs. Ask Claude Code: Create a Next.js project that demonstrates all 10 OWASP Top 10 vulnerabilities in a controlled environment. For each vulnerability, create a page that shows the vulnerable code, explains the attack, and shows the fix. The OWASP Top 10 (2021 edition) includes: Broken Access Control (users accessing resources they should not), Cryptographic Failures (sensitive data exposed due to weak or missing encryption), Injection (SQL injection, XSS, command injection), Insecure Design (flawed architecture that cannot be fixed by implementation), Security Misconfiguration (default credentials, open cloud storage, verbose error pages), Vulnerable Components (outdated dependencies with known CVEs), Authentication Failures (weak passwords, missing MFA, session issues), Data Integrity Failures (code or data modified without verification), Logging and Monitoring Failures (breaches go undetected), and Server-Side Request Forgery (server tricked into making requests to internal resources). Ask Claude Code: For the injection page, create a login form with a SQL injection vulnerability. Show how entering ' OR 1=1 -- as the username bypasses authentication. Then show the fix using parameterised queries. For the XSS page, create a comment form that renders user input without sanitisation. Show how injecting a script tag executes arbitrary JavaScript. Then show the fix using proper output encoding. These hands-on demonstrations make abstract vulnerabilities concrete and memorable.
Input validation and sanitisation
Every piece of data from outside your application is potentially malicious — user input, API responses, URL parameters, cookies, and file uploads. Ask Claude Code: Create a comprehensive input validation library at src/lib/validation.ts using Zod. Build validators for: email addresses (format check plus domain DNS verification for critical flows), passwords (minimum 12 characters, at least one uppercase, one lowercase, one number, no common passwords from a blocklist), usernames (alphanumeric plus underscores, 3 to 30 characters, no reserved words like admin or root), URLs (valid format, no javascript: or data: protocols, optionally verify the domain exists), phone numbers (valid format using a library like libphonenumber), monetary amounts (positive numbers, two decimal places maximum, within reasonable range), and free text (strip HTML tags, limit length, normalise Unicode). For each validator, return either the validated and sanitised value or a descriptive error message. Ask Claude Code: Create an API middleware at src/middleware/validate.ts that validates request bodies against a Zod schema before the route handler executes. If validation fails, return a 400 response with the specific validation errors. Use it on all API routes: export const POST = withValidation(createTaskSchema, handler). This ensures no unvalidated data ever reaches your business logic. Ask Claude Code: Add output encoding for all user-generated content displayed in HTML. Create a utility that escapes HTML entities (less than, greater than, ampersand, quotes) in any string before rendering. In React, JSX escapes by default, but dangerouslySetInnerHTML bypasses this — audit the codebase for any usage and replace it with a proper sanitisation library like DOMPurify. Test each validator with both valid and malicious inputs. Try SQL injection payloads, XSS scripts, path traversal sequences, and Unicode normalisation attacks. Every attack should be caught by validation.
Authentication and session security
Authentication vulnerabilities are the most exploited category. Ask Claude Code: Review and harden the application's authentication system. Implement these security controls. Password hashing: use bcrypt with a cost factor of 12 (minimum). Never store plaintext passwords. Create a test that verifies passwords are hashed in the database. Session management: use HTTP-only, Secure, SameSite=Strict cookies for session tokens. Set session expiry to 24 hours for regular sessions and 30 minutes for administrative sessions. Implement session invalidation on logout that actually removes the server-side session, not just the cookie. Rate limiting on login: allow 5 failed attempts per account per 15 minutes. After 5 failures, lock the account for 15 minutes and send a notification email to the account owner. Implement the lockout with an exponential backoff: 15 minutes, then 30, then 60. Log all failed login attempts with the IP address and timestamp. Ask Claude Code: Add CSRF (Cross-Site Request Forgery) protection. Generate a unique CSRF token per session and include it as a hidden field in all forms and as a custom header in all AJAX requests. Verify the token on the server for every state-changing request (POST, PUT, DELETE). In Next.js with Server Actions, CSRF protection is built in, but for custom API routes you need to add it manually. Ask Claude Code: Implement account recovery securely. The forgot password flow should send a time-limited token (valid for 1 hour) to the email on file. The token should be single-use — once used, it is invalidated. The reset page should not reveal whether the email exists in the system to prevent account enumeration. After a password reset, invalidate all existing sessions for that user to force re-authentication with the new password.
Secrets management and environment security
Leaked secrets are one of the most common causes of breaches. API keys, database passwords, and encryption keys committed to Git repositories are found by automated scanners within minutes. Ask Claude Code: Audit the project for secret management practices. Check for: hardcoded secrets in source code, secrets in Git history (even deleted files remain in history), .env files that might be committed, secrets logged in console output, and secrets exposed in client-side JavaScript bundles. For each finding, show me the vulnerable code and the fix. Create a .env.example file that lists every required environment variable with a description but no actual values. Add .env, .env.local, and .env.production to .gitignore. Verify these files are not tracked by Git with git ls-files. Ask Claude Code: Set up secret rotation procedures. For API keys, create a script that generates a new key from the provider's API, updates the environment variable in Vercel or Railway, verifies the new key works, and revokes the old key. Schedule this quarterly. For database passwords, the procedure is more complex — update the password, update all services that use it, and verify connectivity. Document each procedure as a runbook. Ask Claude Code: Add secret scanning to the CI pipeline. Install gitleaks and run it on every PR. If a secret is detected, the build fails with a clear message explaining the leak and how to fix it. Also add a pre-commit hook (from the automated code review guide) as a local safety net. For secrets that must be available at runtime, use Vercel's encrypted environment variables. Never pass secrets through URL parameters (they appear in server logs and browser history). Never include secrets in error messages or stack traces. Ask Claude Code: Create a secret inventory document that lists every secret the application uses, its purpose, where it is stored, who has access, when it was last rotated, and the rotation procedure. This inventory is essential for incident response — when a breach occurs, you need to know exactly which secrets to rotate.
HTTP security headers and HTTPS
Security headers tell browsers how to behave when loading your site. They are the easiest security wins — each one is a single line of configuration that prevents an entire class of attacks. Ask Claude Code: Add comprehensive security headers to the Next.js application. Create or update next.config.js to add these headers to all responses. Content-Security-Policy: restrict which domains can load scripts, styles, images, and other resources. Start with a restrictive policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:. This prevents XSS attacks by blocking scripts from unauthorised domains. Strict-Transport-Security: max-age=63072000; includeSubDomains; preload — forces HTTPS for 2 years and prevents SSL stripping attacks. X-Content-Type-Options: nosniff — prevents browsers from guessing the content type, blocking MIME-type confusion attacks. X-Frame-Options: DENY — prevents your site from being embedded in iframes, blocking clickjacking attacks. Referrer-Policy: strict-origin-when-cross-origin — controls what URL information is shared when navigating to other sites. Permissions-Policy: camera=(), microphone=(), geolocation=() — disables browser features you do not use. Ask Claude Code: Test the security headers by visiting securityheaders.com with your deployed URL. It grades your headers from A+ to F. Fix any issues until you achieve an A or A+ grade. For the Content-Security-Policy, test thoroughly — a too-restrictive CSP can break legitimate functionality like inline styles, third-party analytics, or embedded content. Use the report-only mode first: Content-Security-Policy-Report-Only with a report-uri to see what would be blocked without actually blocking it. Fix violations, then switch to enforcement mode.
Dependency security and supply chain protection
Your application's security is only as strong as its weakest dependency. Ask Claude Code: Audit all project dependencies for security vulnerabilities. Run npm audit and categorise findings by severity: critical, high, medium, and low. For each finding, show the vulnerable package, the vulnerability type, the affected version range, and the fix (usually a version update). Run npm audit fix to auto-fix what it can. For vulnerabilities that cannot be auto-fixed, evaluate the risk: is the vulnerable code path actually used in your application? If not, document it and add to an audit exceptions file with a justification and a review date. Ask Claude Code: Set up automated dependency updates. Create a GitHub Actions workflow that runs npm audit weekly and opens a PR if new vulnerabilities are found. Add Dependabot configuration at .github/dependabot.yml to automatically create PRs for outdated dependencies. Configure it to update npm packages weekly, limit to 10 open PRs to avoid overwhelming the team, and add security-update labels. For supply chain protection, ask Claude Code: Add npm package verification. Configure .npmrc with package-lock=true to ensure consistent installations. Use npm ci instead of npm install in CI to install exactly what is in the lock file. Add a post-install script that checks for install scripts (which can execute arbitrary code) in new dependencies and flags them for review. Ask Claude Code: Create a dependency review checklist for evaluating new packages before adding them. Check: is the package actively maintained (last commit within 6 months), does it have known vulnerabilities, how many weekly downloads (popularity indicates more eyes on the code), is the licence compatible, does the package run install scripts, and how many transitive dependencies does it add (each one is an additional attack surface). Add this checklist to the project's CONTRIBUTING.md.
Security monitoring and incident response
Prevention is essential but insufficient — you also need detection and response. Ask Claude Code: Set up security monitoring for the application. Create a security event logging system at src/lib/security-logger.ts that logs: failed login attempts with IP and user agent, permission denied errors with the user and resource they tried to access, input validation failures with the field and value (sanitised to avoid logging sensitive data), rate limit hits, and CSRF token mismatches. Each event should include a timestamp, event type, severity, source IP, user ID if authenticated, and a description. Store events in a database table optimised for time-range queries. Ask Claude Code: Build a security dashboard at /admin/security that shows: failed login attempts over the last 7 days grouped by hour, top 10 IPs with the most failed logins, recent permission denied errors, and any unusual patterns like a single IP trying many different usernames (credential stuffing) or a single user accessing many different resources rapidly (enumeration). Add alerting rules: more than 20 failed logins from one IP in an hour triggers an email alert. More than 5 permission denied errors for one user in 10 minutes triggers an alert. Ask Claude Code: Create an incident response plan as a runbook. Define procedures for: suspected data breach (immediate steps: identify scope, contain access, notify affected users within 72 hours as required by GDPR), compromised credentials (rotate all secrets, invalidate all sessions, force password resets), DDoS attack (enable rate limiting, activate CDN protections, communicate with hosting provider), and vulnerable dependency discovered (assess exposure, patch or mitigate, scan for exploitation). Each procedure should have clear steps, responsible parties, communication templates, and a post-incident review process. Test the incident response plan annually with a tabletop exercise — walk through a scenario and verify everyone knows their role.
Security for Developers
This guide is hands-on and practical. The full curriculum covers the conceptual foundations in depth with structured lessons and quizzes.
Go to lesson