Most web application breaches don't come from exotic, zero-day attacks. They come from missing fundamentals: a forgotten authorization check, an unpatched dependency, a secret committed to a public repo, a query built with string concatenation. The good news is that the controls that stop the overwhelming majority of attacks are well understood and largely repeatable. This checklist walks through them in the order you should think about them when shipping a production application.
Treat security as a property of your system, not a phase at the end. The teams that ship securely bake these controls into their templates, CI pipelines, and code review habits so the secure path is the default path.
The OWASP Top 10 in Practice
The OWASP Top 10 is the industry's shorthand for the most common and impactful web application risks. You don't need to memorize the list — you need to know how each category shows up in real code:
- Broken Access Control: A user can access data or actions they shouldn't. The single most common serious vulnerability.
- Cryptographic Failures: Sensitive data sent or stored without proper encryption.
- Injection: Untrusted input interpreted as a command — SQL, NoSQL, OS commands, LDAP.
- Insecure Design: Missing security controls at the architecture level, not just the code level.
- Security Misconfiguration: Default credentials, verbose errors, open cloud buckets, missing headers.
- Vulnerable and Outdated Components: Known-vulnerable dependencies left unpatched.
- Identification and Authentication Failures: Weak passwords, broken session handling, missing MFA.
- Software and Data Integrity Failures: Unverified updates, insecure deserialization, compromised CI/CD.
- Security Logging and Monitoring Failures: Attacks that go undetected because nothing is logged or alerted on.
- Server-Side Request Forgery (SSRF): The server is tricked into making requests to internal resources.
The rest of this checklist maps controls to these categories so you can close the gaps systematically.
Authentication and Session Management
Authentication answers "who are you?" Most teams should not build it from scratch — use a vetted identity provider or library. Whether you build or buy, verify these:
- Passwords hashed with a slow, salted algorithm (bcrypt, scrypt, or Argon2) — never MD5, SHA-1, or plain SHA-256
- Multi-factor authentication available, and required for admin and privileged accounts
- Session tokens are long, random, and stored in
HttpOnly,Secure,SameSitecookies - Sessions expire after inactivity and on logout — tokens are invalidated server-side
- Rate limiting and lockout on login attempts to slow credential stuffing
- Password reset flows use single-use, time-limited tokens and don't reveal whether an email exists
- No credentials, tokens, or session IDs ever appear in URLs or logs
Authorization and Access Control
Authorization answers "what are you allowed to do?" This is where the most damaging bugs hide because they often pass functional testing — the feature works for the developer, who happens to be authorized.
- Every endpoint and action enforces an authorization check on the server — never trust the client to hide a button
- Object-level checks: confirm the current user owns or may access the specific record (prevents Insecure Direct Object Reference / IDOR)
- Deny by default — new routes require explicit permission grants, not the absence of a block
- Use opaque, non-sequential identifiers (UUIDs) so resources can't be enumerated by incrementing IDs
- Administrative functions live behind a separate, hardened access path
- Authorization logic is centralized, not copy-pasted into each handler where it drifts out of sync
Input Validation and Injection
Treat all input as hostile — request bodies, query strings, headers, file uploads, and data from third-party APIs. Two injection classes dominate:
SQL Injection (SQLi)
- Use parameterized queries / prepared statements everywhere — never build SQL by concatenating user input
- Use an ORM or query builder correctly; verify raw-query escape hatches are still parameterized
- Apply least-privilege database accounts so a compromised query can't drop tables or read other schemas
Cross-Site Scripting (XSS)
- Encode output for its context (HTML body, attribute, JavaScript, URL) — modern frameworks do this by default; don't bypass it
- Avoid
dangerouslySetInnerHTML,innerHTML, andevalon user data; sanitize with a trusted library when raw HTML is unavoidable - Enforce a Content Security Policy as a second line of defense against injected scripts
- Validate input against an allowlist (expected type, length, format) rather than trying to blocklist bad characters
Dependency and Supply-Chain Risk
Modern apps are mostly third-party code. A vulnerability in any dependency — direct or transitive — is your vulnerability. Supply-chain attacks also target the build pipeline itself.
- Automated dependency scanning in CI (Dependabot, Snyk, npm audit, or equivalent)
- A documented timeline for patching critical and high-severity advisories
- Lockfiles committed so builds are reproducible and dependencies can't silently change
- Pin and verify build tooling; protect CI/CD secrets and require signed commits or provenance where possible
- Remove unused dependencies — the smallest attack surface is the code you don't ship
- Maintain a software bill of materials (SBOM) for anything you distribute
Secrets Management
Leaked secrets are a top cause of breaches because one credential often unlocks everything behind it.
- No secrets in source code, config files, or client-side bundles — ever
- Secrets stored in a dedicated manager (AWS Secrets Manager, Vault, Doppler) or platform-encrypted environment variables
- Secret scanning in CI and pre-commit hooks to catch accidental commits before they land
- Rotate credentials on a schedule and immediately after any suspected exposure
- Scope each key to least privilege so a leak has limited blast radius
Security Headers, HTTPS, and TLS
HTTP response headers and transport security are cheap, high-leverage controls that close entire vulnerability classes.
- HTTPS everywhere with TLS 1.2 minimum (prefer 1.3); redirect all HTTP to HTTPS
- HSTS (
Strict-Transport-Security) so browsers refuse to connect over plain HTTP - Content-Security-Policy to restrict where scripts, styles, and frames can load from
X-Content-Type-Options: nosniffto stop MIME-type confusionX-Frame-Optionsor CSPframe-ancestorsto prevent clickjackingReferrer-Policyto limit leaking URLs to third parties- Keep TLS certificates current with automated renewal; disable weak ciphers and protocols
Rate Limiting and Abuse Prevention
- Rate limit authentication, password reset, and other sensitive endpoints per IP and per account
- Throttle or queue expensive operations to prevent resource-exhaustion denial of service
- Validate and constrain file uploads (type, size, and storage outside the web root)
- Use CAPTCHA or proof-of-work on public, automatable forms to deter bots
- Put a WAF or CDN in front of the app to absorb common automated attacks
Logging and Monitoring
You can't respond to what you can't see. The goal is to detect an attack in progress, not read about it in a breach disclosure.
- Log authentication events, access-control failures, and high-value actions with enough context to investigate
- Never log secrets, full payment data, or passwords
- Centralize logs and alert on anomalies — spikes in 401/403s, unusual geographies, mass data export
- Retain security-relevant logs long enough to support incident investigation
- Maintain a written, tested incident response plan with clear escalation paths
Pre-Launch Security Checklist
Run through this before any production release:
- All endpoints enforce authentication and object-level authorization
- Queries are parameterized; output is context-encoded; CSP is in place
- Dependencies scanned clean of critical advisories; lockfile committed
- No secrets in code; secret scanning passing in CI
- HTTPS enforced, HSTS enabled, security headers configured
- Rate limiting active on auth and sensitive endpoints
- Error messages are generic in production; stack traces and debug modes disabled
- Logging and alerting verified end to end; incident response plan documented
- An independent review or penetration test completed for anything handling sensitive data
Frequently Asked Questions
Do small apps really need all of this?
The fundamentals — authorization checks, parameterized queries, HTTPS, dependency scanning, and secrets management — apply to every app regardless of size. Attackers scan the entire internet automatically; being small doesn't make you invisible. Larger or more sensitive apps add penetration testing, formal logging, and stricter rate limiting on top.
What's the single most important control?
Access control. Broken authorization is consistently the most common serious vulnerability, and it's the hardest to catch with automated scanners because the feature appears to work for whoever tests it. Enforce server-side, object-level checks on every action and deny by default.
How often should we run a security review?
Automated scanning should run on every commit through CI. A deeper manual review or third-party penetration test is worth doing annually, and additionally before any major release that changes authentication, payments, or how sensitive data is handled.
Should we build authentication ourselves?
Usually not. Authentication has many subtle failure modes — session fixation, timing attacks, insecure resets — that vetted providers and libraries have already solved. Use a trusted identity solution and spend your effort on authorization logic, which is specific to your domain and can't be outsourced.
Open Door Digital builds and reviews production web applications with security designed in from the first commit. Talk to our team about a security review or hardening your existing app.
Related reading: SOC 2 Compliance Checklist for SaaS Companies, DevSecOps Implementation Guide, and Data Privacy Compliance.