Most web application breaches do not involve exotic zero-day exploits. They exploit a small set of well-understood weaknesses — unvalidated input, broken access control, weak authentication, exposed secrets, and outdated dependencies — that have been on security checklists for years. The encouraging implication is that getting the fundamentals right defends against the large majority of real-world attacks. Security is not a feature you add at the end; it is a set of habits applied throughout development. Here are the ones that matter most.
Never Trust Input
The root cause of a remarkable number of vulnerabilities is trusting data that came from outside your system — form fields, URL parameters, headers, file uploads, API payloads. Treat all of it as potentially hostile and validate it on the server.
Validation means checking that input matches what you expect — type, format, length, range — and rejecting what does not. A schema validation library makes this systematic:
import { z } from "zod";
const SignupInput = z.object({
email: z.string().email(),
age: z.number().int().min(13).max(120)
});
// Throws on anything that does not match the contract
const data = SignupInput.parse(req.body);
Validate on the server even if you also validate on the client. Client-side checks improve the user experience but are trivially bypassed; they are never a security boundary.
Prevent Injection
Injection happens when untrusted input is interpreted as code or commands. The two most common forms are SQL injection and cross-site scripting (XSS).
For SQL injection, never build queries by concatenating strings. Use parameterized queries or an ORM, which separate the query structure from the data:
// Vulnerable
db.query(`SELECT * FROM users WHERE email = '${email}'`);
// Safe — parameterized
db.query("SELECT * FROM users WHERE email = $1", [email]);
For XSS, the danger is rendering untrusted content as HTML. Modern frameworks like React escape content by default, which prevents most XSS — but you reopen the hole the moment you use an escape hatch like dangerouslySetInnerHTML with unsanitized input. If you must render user-supplied HTML, sanitize it with a vetted library first, and add a Content Security Policy as a second line of defense.
Get Authentication and Authorization Right
Authentication proves who a user is; authorization controls what they may do. Both are frequent failure points.
For authentication, lean on vetted libraries or identity providers rather than rolling your own. If you do handle passwords, store them with a strong, slow hashing algorithm (bcrypt, scrypt, or Argon2) — never plain text, never fast general-purpose hashes. Enforce reasonable password rules, rate-limit login attempts, and offer multi-factor authentication.
For authorization, the cardinal rule is to check permissions on the server for every protected action, against the authenticated user's real identity. Broken access control — where the server trusts a client-supplied role or fails to verify ownership of a resource — is one of the most exploited vulnerability classes. Hiding a button in the UI is not access control; the API behind it must enforce the rule.
// Enforce ownership server-side, not just in the UI
if (resource.ownerId !== session.userId) {
return new Response("Forbidden", { status: 403 });
}
Protect Data in Transit and at Rest
Serve everything over HTTPS — no exceptions — and enforce it with HTTP Strict Transport Security (HSTS) so browsers refuse to connect insecurely. Keep secrets (API keys, database credentials, signing keys) out of source control and out of client-side code; load them from environment variables or a secrets manager. A secret committed to a repository should be treated as compromised and rotated, even in a private repo.
Set security headers to harden the browser's behavior:
- Content-Security-Policy to restrict where scripts, styles, and resources can load from — a strong XSS mitigation.
- X-Content-Type-Options: nosniff to stop MIME-type sniffing.
- Referrer-Policy and Permissions-Policy to limit information leakage and feature access.
- Mark cookies
HttpOnly,Secure, andSameSiteto protect session tokens from theft and cross-site request forgery.
Keep Dependencies Healthy
A modern web app runs far more third-party code than first-party code, and that dependency tree runs with your application's privileges. A vulnerability deep in a transitive dependency is your vulnerability. Audit regularly, keep packages current, and minimize how many you add:
npm audit # surface known vulnerabilities
npm audit fix # apply non-breaking fixes
Automate this in continuous integration so a newly disclosed vulnerability surfaces in a pull request rather than in an incident. Be especially cautious adding dependencies for trivial functionality — every package is attack surface and supply-chain risk.
Common Pitfalls
- Trusting client-side validation. It is a UX feature, never a security control. Re-validate everything on the server.
- Verbose error messages in production. Stack traces and internal details help attackers. Log them server-side; show users a generic message.
- Security through obscurity. Hiding an endpoint or a UI element is not protection. Enforce real authorization on the server.
- Secrets in client bundles. Anything shipped to the browser is public. Keep secrets on the server.
Conclusion
Web application security is mostly the disciplined application of a few fundamentals: never trust input, prevent injection, get authentication and authorization right, protect data in transit and at rest, and keep dependencies healthy. None of these require deep specialist knowledge — they require consistency, applied on every endpoint and every feature. When we build for clients at Codememory, security review is part of the development process rather than a pre-launch scramble, because the cost of a breach — to data, to trust, and to the business — dwarfs the modest, ongoing cost of doing the fundamentals well.
Frequently asked questions
Injection flaws and broken access control consistently top the OWASP lists. Both come from trusting input or a user's claimed permissions without verification. The defense — never trust input, always check authorization on the server — addresses a huge share of real breaches.
It is possible but risky. Authentication has many subtle failure modes — password storage, session handling, reset flows, rate limiting. For most projects, using a vetted authentication library or provider is safer and faster than building from scratch.
Always on the server, and optionally on the client for user experience. Client-side validation can be bypassed trivially, so it is never a security control. The server must validate every input as if the client cannot be trusted, because it cannot.
Audit them regularly, keep them updated, and minimize how many you pull in. Automated tooling can flag known vulnerabilities in your dependency tree. Treat a flagged transitive dependency as seriously as your own code, because it runs with the same privileges.



