Web Security Best Practices Every Developer Should Follow
Security is not a feature you bolt on at the end of a project — it is a habit you build into every line of code you write. At TCCB Solutions, we have seen how a single overlooked input field or a leaked API key can undo months of work. The good news is that the majority of real-world breaches come down to a handful of preventable mistakes. In this post, we want to share the practical, day-to-day practices we follow to keep the applications we build safe.
Never Trust User Input
The golden rule of web security is simple: treat every piece of incoming data as hostile until proven otherwise. This applies to form fields, URL parameters, headers, cookies, and even data coming back from third-party APIs. The two most common attacks — SQL injection and cross-site scripting (XSS) — both stem from trusting input that should never have been trusted.
For database access, we always use parameterized queries or prepared statements rather than string concatenation. Compare these two approaches:
// Dangerous — never do this
const q = "SELECT * FROM users WHERE email = '" + email + "'";
// Safe — parameterized
const q = "SELECT * FROM users WHERE email = ?";
db.query(q, [email]);
The parameterized version ensures the database treats email strictly as a value, never as executable SQL. The same principle applies on the output side: always encode data before rendering it in HTML to neutralise XSS.
Validate and Sanitise on the Server
Client-side validation is great for user experience, but it offers zero security — anyone can bypass it with a few keystrokes in the browser console or a simple curl request. We treat the server as the only authority. Use an allowlist approach where possible: define exactly what valid data looks like and reject everything else, rather than trying to blocklist known-bad patterns.
- Enforce strict types, lengths, and formats on every field.
- Reject unexpected fields instead of silently ignoring them.
- Normalise data (trim whitespace, lowercase emails) before storing it.
Get Authentication and Sessions Right
Password handling is an area where rolling your own solution almost always ends badly. We never store passwords in plain text or with fast hashes like MD5. Instead, we use a purpose-built, slow hashing algorithm such as bcrypt or argon2, which are designed to resist brute-force attacks.
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash(password, 12);
// later...
const match = await bcrypt.compare(attempt, hash);
Beyond hashing, we enforce strong session practices: set cookies with the HttpOnly, Secure, and SameSite flags, rotate session identifiers after login, and add rate limiting to login endpoints to slow down credential-stuffing attempts. For anything sensitive, we encourage multi-factor authentication.
Manage Secrets Carefully
One of the most common mistakes we see is hard-coded credentials committed to a repository. API keys, database passwords, and tokens should never live in your source code. We keep them in environment variables or a dedicated secrets manager, and we make sure files like .env are listed in .gitignore from day one. If a secret is ever exposed, rotate it immediately — assume it is already compromised.
Use HTTPS and Security Headers Everywhere
Serving traffic over HTTPS is non-negotiable in 2026. Encryption in transit protects your users from eavesdropping and tampering. On top of that, a few well-chosen HTTP response headers add meaningful defence in depth:
Content-Security-Policy— limits where scripts and resources can load from, a strong defence against XSS.Strict-Transport-Security— forces browsers to use HTTPS.X-Content-Type-Options: nosniff— stops MIME-type guessing.X-Frame-Options— protects against clickjacking.
Keep Dependencies Patched
Modern applications are built on dozens of open-source packages, and vulnerabilities in those packages are a leading cause of breaches. We run automated dependency scans as part of our build pipeline and treat npm audit or equivalent warnings as real work, not background noise. Staying current is far cheaper than recovering from an exploit.
Build Security Into Your Workflow
None of these practices are difficult on their own — the challenge is applying them consistently. We bake security checks into code reviews, automate what we can, and treat every new feature as an opportunity to ask, "how could this be abused?" That mindset, more than any single tool, is what keeps applications resilient over time.
If you would like a fresh set of eyes on your application's security posture, or you are building something new and want it done right from the start, we would love to hear from you. Let's build something secure together.