Web App Security: The Checklist We Actually Use

A practical security checklist for web applications — the vulnerabilities that actually get exploited, the fixes that matter, and what to check before you call a project done.

Web App Security: The Checklist We Actually Use

Most security advice is either terrifying or useless. The terrifying kind lists every CVE ever published; the useless kind tells you to "use HTTPS". The truth is in between: a small set of vulnerabilities accounts for almost all real-world breaches, and fixing them is mostly about discipline, not exotic tooling.

Here's the checklist we run on every SrcTeam project before it ships.

Authentication and sessions

  • Hash passwords with a modern algorithmbcrypt or argon2id. Never MD5, never SHA-256, never "encrypted" passwords. In Laravel, the default Hash facade is already correct — just don't override it.
  • Rate-limit login endpoints. Five attempts per minute per IP and per account is a reasonable default. This kills the vast majority of credential-stuffing attacks.
  • Rotate session IDs on login and set a sane session lifetime. A session that lives for a year is a session that gets stolen.
  • Use MFA for admin accounts. Even a simple TOTP app blocks most account takeovers.

Authorization: the part everyone forgets

Authentication is "who are you?" — authorization is "what can you do?". The second one is where most bugs live.

  • Check authorization on every request, not just in the UI. Hiding a button is not a security control. The server must re-verify that the user may perform the action.
  • Use a policy or middleware layer (Laravel policies, Vue Router guards are not enough) so the check lives in one place.
  • Watch for IDOR — "insecure direct object references". If a URL contains ?id=42, make sure user 42 is allowed to see it. This is the single most common bug we find in codebases we take over.

Input and output

  • Validate everything, trust nothing. Whitelist what you accept: types, lengths, formats. Reject the rest.
  • Parameterized queries only. If you're concatenating user input into SQL, stop. ORMs and query builders exist precisely so you never write raw SQL with variables.
  • Escape output by default. Modern frameworks (Laravel's Blade, Vue's template syntax) escape automatically — use the framework's escaping, not v-html or raw echo, unless you have a very good reason.
  • Uploads are code until proven otherwise. Store them outside the web root or serve them with a Content-Disposition header, validate the MIME type server-side, and never trust the client-provided filename.

The boring infrastructure layer

  • HTTPS everywhere, with HSTS. Redirect HTTP to HTTPS at the server level, not in the app.
  • Security headers — a minimal set: X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy, and a Content-Security-Policy that starts strict and gets loosened only where needed.
  • Keep dependencies updated. Most breaches exploit known, patched vulnerabilities. A weekly composer audit / npm audit run in CI catches them before they ship.
  • Least privilege on the server. The app runs as its own user, the database user can only touch its own database, and SSH keys are per-person with passphrases.

What we don't bother with

  • Security through obscurity — renaming the admin URL is fine as a speed bump, not a control.
  • Over-engineering. A WAF in front of a static site is theater. Spend the same budget on the checklist above.
  • Panic. Security is a process, not a state. The goal is to be boringly, consistently safe — not to be the most paranoid team in the room.

The checklist takes an afternoon to apply and saves the kind of week you don't want to have. If you're not sure where your project stands, start with the authentication and authorization sections — that's where the damage happens.