Web Application Security Best Practices
The controls that actually prevent the breaches you read about — auth, secrets, dependency hygiene, logging, and the boring habits that compound.
- Published
- Updated
- Reading time
- 8 min
Most web application breaches are not the result of a clever attacker exploiting a zero-day. They are the result of an unpatched dependency, a leaked secret in a public repo, an admin endpoint left open, or a session token that did not expire. The controls that prevent the breaches you read about are boring, well-understood, and routinely skipped.
This post is the short list of what actually matters, in the order it matters.
Secrets never live in the repo
API keys, database passwords, signing keys, third-party tokens — they live in a secret manager (AWS Secrets Manager, HashiCorp Vault, Doppler) and they are injected as environment variables at deploy time. They are never committed, never shared in chat, never pasted into a config file. The day someone commits a secret, the secret manager detects it, the secret rotates, the audit log records the incident, and the next engineer has a clean repo.
A secret in a public repo is not a mistake that gets fixed. It is a mistake that gets scraped, indexed and used within minutes. The detection has to be automatic, and the rotation has to be one command.
Authentication and authorisation are not the same thing
Authentication is "who is this user". Authorisation is "what is this user allowed to do". Frameworks solve the first by giving you a session, a JWT or a session cookie. None of them solve the second for you. The role-aware authorisation layer — the one that says "this user can read this record but not that one" — is the part you have to design and build.
A clean authorisation layer sits in front of every endpoint, takes the authenticated principal, the requested resource and the action, and returns yes or no. It is a single function, called from a single place, with the role and the permission model that the business actually needs. When the customer asks "can the finance team see invoices but not the support team", the answer is a config change, not a code change.
Dependency hygiene is a weekly chore
The dependency tree of a typical Node.js or Python application has 500+ packages. Each one has a CVE lifecycle. The right answer is automated: Dependabot or Renovate opens the PR, the CI runs the tests, the security team audits the diff, and the patch ships. The wrong answer is a quarterly "let's update our dependencies" project that becomes a six-week refactor.
Run an SBOM (software bill of materials) generator on every release. Subscribe to the GitHub security advisories for the packages you depend on. Pin the versions. The boring hygiene is the difference between a 24-hour patch and a six-week incident.
Log what the auditor will ask for
Authentication events, authorisation failures, data exports, admin actions, configuration changes. These are the events the SOC 2 auditor, the ISO 27001 auditor, or the regulator will ask for, and they are the events the engineer needs at 2am when something looks wrong. The log format is structured (JSON), the retention is set by policy, the access is restricted, and the log itself is not the source of the data — it is the trail of what happened.
A log you cannot search is a log you cannot use. A log without timestamps and a correlation id is a log that ties to nothing. The format matters, and the format is the boring part.
What to skip
You do not need a custom WAF on day one. You do not need a SIEM. You do not need a penetration test every quarter. The right answer for the first 18 months is a managed WAF (Cloudflare, AWS WAF), a managed log store (CloudWatch, Datadog), an annual external penetration test, and the boring hygiene above. The expensive controls come when the compliance review and the customer base justify them, not before.
If you want a second opinion on the security posture, our cybersecurity team can run the review. If the question is "we have an incident and we need help", our application support is the right engagement.
Key takeaways
- Secrets in environment variables, not in the repo — the breach you prevent is the one nobody reads about.
- Authentication and authorisation are two different problems; both have to be solved, neither is solved by the framework.
- Dependency hygiene is a weekly chore, not a quarterly project — automate the update, audit the diff, ship the patch.
- Log the security events the auditor will ask for, in a format the engineer can read at 2am.
Ready to put this into practice?
Let’s build the solution that gets you there.