How do you secure a frontend application against common attacks?
Layered defense: HTTPS + HSTS, secure cookies (HttpOnly+Secure+SameSite), Content-Security-Policy (script-src nonces, no unsafe-inline), framework auto-escape + DOMPurify for HTML, CSRF tokens or origin check on state-changing requests, parameterized queries (SQL/NoSQL), input validation at boundaries (Zod), per-endpoint rate limit, secrets in env (never in code), dependency scanning (npm audit, Dependabot), least-privilege auth scopes, monitoring + alerting on anomalies. OWASP Top 10 is the canonical checklist.
Security is layered — no single control is sufficient. The goal is defense-in-depth: each layer catches what others miss.
1. Transport
- HTTPS everywhere — no HTTP fallback.
- HSTS (
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload) — force HTTPS for future requests, even if user types http://. - Modern TLS (TLS 1.2+, prefer 1.3).
- HSTS preload list for max effect.
2. Authentication
- Bcrypt / Argon2 for password hashing (never MD5/SHA1/plain).
- MFA (TOTP, WebAuthn, passkeys) for sensitive accounts.
- OAuth / OIDC for federated login; PKCE for SPA flows.
- Short-lived access tokens + refresh token rotation.
- Rate limit login attempts (per IP and per account).
- Account lockout after N failures, with notification.
3. Session / cookies
Set-Cookie: session=abc; HttpOnly; Secure; SameSite=Lax; Max-Age=3600- HttpOnly: JS can't read.
- Secure: HTTPS only.
- SameSite=Lax: cross-site CSRF defense (browser default).
- Short Max-Age + rotation.
- Logout invalidates server-side (don't rely on cookie expiry alone).
4. CSRF
- SameSite=Lax cookies (already strong default).
- Plus CSRF token (double-submit cookie or synchronizer token) for state-changing endpoints.
- Validate Origin / Sec-Fetch-Site headers.
- Reject GET for mutations.
5. XSS
- Framework auto-escape ({value} in React).
- DOMPurify for unavoidable HTML.
- No innerHTML / eval / new Function with user input.
- URL scheme validation (block
javascript:). - CSP as defense-in-depth:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-abc' 'strict-dynamic';
object-src 'none';
base-uri 'none';- Trusted Types in Chromium for DOM XSS elimination.
6. Injection (SQL, NoSQL, command, LDAP)
- Parameterized queries always. Never string-concatenate SQL.
- ORM with prepared statements (Prisma, Drizzle, TypeORM).
- For shell: never
exec(userInput). Use library APIs. - For NoSQL: avoid user-provided operators (
$where,$exprin MongoDB).
7. Authorization
- Server-enforced, never client-controlled. The browser-visible UI hides admin features; the server also checks "is this user allowed?"
- Least privilege: each role gets only what it needs.
- Object-level: "can user X access record Y?" — check on every request.
- Don't expose internal IDs that allow guessing other users' resources.
8. Input validation
- At boundaries: Zod / Yup / Joi for request bodies, query params, form data.
- Validate types AND values AND ranges.
- Allowlist > blocklist.
9. Headers
Strict-Transport-Security: max-age=31536000; includeSubDomains
Content-Security-Policy: …
X-Content-Type-Options: nosniff
X-Frame-Options: DENY ← or frame-ancestors in CSP
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()10. Rate limiting + abuse
- Per-endpoint per-user limit.
- Per-IP limit (with CDN/WAF help).
- Stricter limits on auth + signup.
- Capcha (hCaptcha, reCAPTCHA) for high-risk flows.
- Account creation throttling to limit sybil attacks.
11. Secrets
- Environment variables or secret manager (AWS Secrets Manager, Vault).
- Never in code or git.
- Rotate periodically.
- Per-environment (dev / staging / prod different).
- Scan git history (truffleHog, GitGuardian) to catch accidental commits.
12. Dependencies
- npm audit / Dependabot for known CVEs.
- Pin versions (no
^for security-critical deps). - Audit before adding new deps (bundlephobia + GitHub stars + maintenance signals).
- SCA tools (Snyk, Socket.dev).
- Lockfile committed.
13. File uploads
- Validate file type by magic bytes (not just MIME).
- Cap file size.
- Scan for malware if user-facing.
- Don't serve uploads from your origin — use a separate origin or CDN to limit XSS risk.
- Store outside webroot; serve with explicit Content-Type + Content-Disposition.
14. Logging + monitoring
- Log auth events (login, logout, password reset, MFA enroll).
- Alert on anomalies (impossible travel, spike in 401s, password spraying).
- Don't log secrets / passwords / tokens.
- Centralize (Datadog, Splunk, ELK).
- Sentry for application errors.
15. Pen testing + bug bounty
- Periodic external pen test (annually for production apps).
- Bug bounty program (HackerOne, Bugcrowd) once mature.
- Internal red team for high-risk products.
16. SDLC
- Code review on every PR.
- SAST (Semgrep, CodeQL, ESLint security plugins).
- DAST (ZAP, Burp) in staging.
- Security training for engineers.
- Threat modeling for new features.
OWASP Top 10
The canonical list of common web vulnerabilities. Current categories:
- Broken Access Control
- Cryptographic Failures
- Injection
- Insecure Design
- Security Misconfiguration
- Vulnerable and Outdated Components
- Identification and Authentication Failures
- Software and Data Integrity Failures
- Security Logging and Monitoring Failures
- Server-Side Request Forgery (SSRF)
Audit against this list as a baseline.
Mental model
Security is process + defense in depth. No single header / library / lint rule is enough. Layer transport, auth, sessions, input validation, output encoding, CSRF defense, CSP, secrets management, dependency scanning, rate limiting, monitoring. Make secure default and insecure require explicit opt-out.
Follow-up questions
- •What's the difference between authentication and authorization?
- •How does SameSite=Lax combine with CSRF tokens?
- •What should you scan with Dependabot / Snyk?
- •How do you do threat modeling?
Common mistakes
- •Tokens in localStorage — XSS exfiltrates.
- •String-concat SQL — injection.
- •Trusting client-side authorization checks.
- •Secrets in git history.
- •No rate limit on login — brute force.
- •MD5/SHA1 for password hashing.
Performance considerations
- •Most security headers have zero perf cost. Bcrypt/Argon2 have intentional CPU cost — tune work factor for ~100ms per hash. Rate limiting requires fast lookup (Redis). CSP/CSRF token checks are negligible.
Edge cases
- •SSRF via user-provided URLs — server fetches internal endpoints.
- •Open redirect — phishing vector.
- •Race conditions in critical operations (token use, payment processing).
- •Time-of-check vs time-of-use bugs.
- •Cache poisoning via Vary header misconfiguration.
Real-world examples
- •OWASP ASVS is a detailed standard for app security verification.
- •PortSwigger Web Security Academy is the canonical training.
- •Major breaches (Equifax, MyFitnessPal, etc.) usually trace to one or two missed controls — not novel attacks.