Kuboid Secure Layer LogoKuboid Secure Layer
Back to Intelligence
February 28, 2026Vinay KumarSecure Coding

Secure Coding Practices for Web Developers — Quick Wins That Actually Work

Cover Image for Secure Coding Practices for Web Developers — Quick Wins That Actually Work

Secure Coding Practices for Web Developers — Quick Wins That Actually Work

TLDR: Security doesn't have to be a separate phase, a department, or an expensive engagement. Most of the vulnerabilities I find in web applications would have been prevented by a handful of habits that add less than 30 minutes to a developer's week. This post covers those habits — specific, framework-agnostic, and implementable today — along with the two free resources every developer serious about security should know about.


The Philosophy: Security as a Habit, Not a Checklist

Checklists are run once and filed. Habits are what you do automatically, without thinking about it, every time.

The difference matters because vulnerabilities aren't introduced once — they're introduced continuously, with every new feature, every refactor, every new developer who joins the team and inherits existing patterns. A one-time security audit catches what existed at that moment. Security habits mean the next feature starts from a more defensible position than the last one.

None of the practices below require a security background. They require knowing the pattern and applying it consistently. That's it.


The Quick Wins

1. Always Use Parameterised Queries — Never Concatenate SQL

If you take one thing from this entire post, let it be this one. SQL injection is decades old, completely preventable, and still appears in production codebases in 2025.

# Vulnerable
query = "SELECT * FROM users WHERE email = '" + user_input + "'"

# Secure
cursor.execute("SELECT * FROM users WHERE email = %s", (user_input,))

Every major database library in every major language supports parameterised queries. There is no performance trade-off. There is no complexity argument. There is no context in which building SQL queries through string concatenation is acceptable in production code.

2. Always Encode Output Before Rendering User-Supplied Data

When user-supplied content is rendered into HTML — comments, profile names, search queries, review text — encode it for the context it's going into before it touches the page. In most modern frameworks this happens by default in template rendering, but it breaks down the moment you use innerHTML, dangerouslySetInnerHTML, or document.write.

The habit: whenever you write code that places dynamic data into the DOM outside of standard template rendering, stop and ask whether that data is encoded. We covered the full mechanics in our XSS post — the short version is that one unencoded injection point is enough.

3. Add Authorisation Checks at the Data Layer, Not Just the Route Layer

Checking whether a user is logged in at the route level is authentication. Checking whether the logged-in user is allowed to access this specific object is authorisation — and it needs to happen at the data query level, not just at the door.

// Route-level check only — still vulnerable to IDOR
router.get('/invoice/:id', requireAuth, async (req, res) => {
  const invoice = await Invoice.findById(req.params.id); // anyone authenticated can access any invoice
  res.json(invoice);
});

// Data-level authorisation — correct
router.get('/invoice/:id', requireAuth, async (req, res) => {
  const invoice = await Invoice.findOne({ _id: req.params.id, userId: req.user.id });
  if (!invoice) return res.status(403).json({ error: 'Forbidden' });
  res.json(invoice);
});

This is the fix for IDOR — the most commonly found vulnerability in the OWASP Top 10. One line of additional query logic that eliminates an entire attack class.

4. Set Security Headers — 10-Minute Setup

Five HTTP headers that your server sends with every response — requiring a few lines of configuration — protect against a range of browser-level attacks:

  • Content-Security-Policy — restricts what scripts, styles, and resources the browser will load
  • Strict-Transport-Security — forces HTTPS, preventing downgrade attacks
  • X-Frame-Options: DENY — blocks your site from being embedded in iframes (clickjacking prevention)
  • X-Content-Type-Options: nosniff — prevents MIME type sniffing
  • Referrer-Policy: strict-origin-when-cross-origin — controls what referrer data is sent cross-origin

For Node/Express, the Helmet library applies sensible defaults for all of these with a single app.use(helmet()) call. Check your current header configuration at securityheaders.com — it grades your setup and shows exactly what's missing.

5. Audit Your Dependencies Weekly

Your application's security posture is a product of your code and every library it depends on. A vulnerability in a transitive dependency — a package your package depends on — is a vulnerability in your application.

npm audit          # Node.js
pip audit          # Python
bundle audit       # Ruby

Run these as part of your regular workflow, not just before a release. Set up Dependabot on your GitHub repository — it files pull requests automatically when vulnerabilities are published for your dependencies. It takes five minutes to enable and removes the cognitive overhead of manual tracking.

6. Never Commit Secrets to Git

API keys, database credentials, private tokens — these must never appear in source code. Once committed, they're in the git history permanently, even after deletion, and accessible to everyone with repository access — including former team members and, if the repository is ever made public, the entire internet.

# Scan your entire commit history for secrets before you push
npx trufflehog git file://. --since-commit HEAD~10

Use .env files locally, add .env to .gitignore on day one, and use a secrets manager or environment variable injection in production. If a secret has already been committed, rotate it immediately — removing the commit is not sufficient.

7. Rate Limit Every Endpoint That Accepts Credentials

Login endpoints, password reset forms, OTP verification, email confirmation — any endpoint that accepts something sensitive needs rate limiting. Without it, automated tools can make tens of thousands of attempts per minute unimpeded.

In Express, express-rate-limit is a five-line implementation:

const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({windowMs: 15 * 60 * 1000, max: 10});
app.use('/auth/login', loginLimiter);

Ten attempts per 15-minute window is reasonable for a login endpoint. Adjust per your application's needs. The point is: some limit, applied consistently, on every sensitive endpoint.

8. Set HttpOnly and Secure Flags on Session Cookies

Two flags, one line of configuration, meaningful impact:

  • HttpOnly — prevents JavaScript from reading the cookie, blocking the most common XSS exploit (session cookie theft via document.cookie)
  • Secure — ensures the cookie is only transmitted over HTTPS, preventing interception on unencrypted connections
// Express session configuration
app.use(session({
  secret: process.env.SESSION_SECRET,
  cookie: { httpOnly: true, secure: true, sameSite: 'strict' }
}));

SameSite: strict while you're there — it significantly reduces CSRF risk without requiring a separate CSRF token implementation in most cases.


The 5-Minute Weekly Security Habit

At the end of each sprint, before you close your last PR of the week, spend five minutes on this question: "What did I ship this week, and how would I try to break it?"

Pick one feature. Think about the inputs it accepts. Think about the data it returns. Think about who's allowed to do what — and whether the code actually enforces that. Think about whether you added any new dependencies and whether you checked them.

You won't catch everything this way. But you'll catch the obvious things — the forgotten auth check, the unencoded output, the hardcoded test credential that didn't get removed. Most of the vulnerabilities I find in pen tests are obvious in retrospect. This habit makes them obvious before they ship.


Two Free Resources Worth Bookmarking

OWASP Cheat Sheet Series — practical, implementation-specific guidance on every major vulnerability class: SQL injection prevention, XSS prevention, authentication implementation, session management, and more. Organised by topic, kept current by the security community. This should be your first reference when you're implementing something security-relevant for the first time.

PortSwigger Web Security Academy — free, hands-on labs that teach web vulnerability exploitation and prevention through interactive exercises. If you want to understand how IDOR, XSS, SSRF, or SQLi actually work from an attacker's perspective — not just theoretically, but through practice — this is the best free resource available. Completing a few labs will change how you read your own code.


How Does Your Current Codebase Hold Up?

Habits prevent the vulnerabilities you know to look for. A security assessment surfaces the ones you don't. The two complement each other — better habits mean fewer findings, and fewer findings mean more focused attention on the ones that actually matter.

If you want an objective view of where your codebase currently stands, we're glad to take a look. Get in touch to start with a scoping conversation, explore our services, or read more about how we work.


More practical security guides for developers and founders are on the Kuboid Secure Layer blog.

Vinay Kumar
Vinay Kumar
Security Researcher @ Kuboid
Get In Touch

Let's find your vulnerabilities before they do.

Tell us about your product and we'll tell you what we'd attack first. Free consultation, no commitment.

  • 📧support@kuboid.in
  • ⏱️Typical response within 24 hours
  • 🌍Serving clients globally from India
  • 🔒NDA available before any discussion
Loading form...