NeedSec logo
Secure Web Development: 10 Essential Practices Every Development Team Should Follow
← Back to Blog
Secure Development19 May 20266 min read

Secure Web Development: 10 Essential Practices Every Development Team Should Follow

Most web application vulnerabilities are not discovered during a penetration test — they are built in during development. This guide covers ten security practices that every development team should implement from the start, not bolt on at the end.

Why Security Belongs in Development, Not Just Testing

Penetration testing finds vulnerabilities. Secure development prevents them. The two work together, but the most cost-effective place to fix a security flaw is before it ships — not after a tester finds it in production.

This guide covers ten practices that address the most common classes of vulnerability found during web application penetration tests. Each one is practical, implementable without specialist tooling, and directly reduces the risk of a serious finding.


1. Validate and Sanitise Every Input

Never trust data that comes from outside your application boundary — this means user input, query parameters, headers, cookies, and third-party API responses.

What to do:

  • Define an allowlist of what input is expected (type, length, format, character set)
  • Reject anything that does not match before it reaches your business logic
  • Never rely on frontend validation as a security control — it is trivially bypassed
  • Use parameterised queries for all database operations (see SQL injection below)

Input validation is the single most impactful control across the OWASP Top 10. BOLA, injection, XSS, and path traversal all rely on unexpected input reaching the wrong place.


2. Use Parameterised Queries — No Exceptions

SQL injection remains one of the most critical and frequently found vulnerabilities in web applications. It happens when user input is concatenated directly into a SQL query.

// Vulnerable
const query = "SELECT * FROM users WHERE email = '" + email + "'";

// Safe — parameterised
const result = await db.query("SELECT * FROM users WHERE email = $1", [email]);

Modern ORMs (Prisma, Sequelize, TypeORM, Django ORM, ActiveRecord) use parameterised queries by default. Avoid raw query methods unless you fully understand the implications, and never interpolate user-controlled values into them.


3. Implement Output Encoding to Prevent XSS

Cross-site scripting (XSS) occurs when untrusted data is rendered in a browser without proper encoding. An attacker can inject scripts that steal session tokens, redirect users, or perform actions on their behalf.

What to do:

  • Use a templating engine or frontend framework (React, Vue, Angular) that encodes output by default
  • Never use innerHTML, dangerouslySetInnerHTML, document.write, or eval with user-controlled data
  • Set a strong Content Security Policy (CSP) header as a defence-in-depth control
  • When rendering user-generated HTML (e.g. rich text), use a dedicated sanitisation library such as DOMPurify
Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'

4. Enforce Proper Authentication and Session Management

Weak authentication is consistently one of the top findings in web application penetration tests.

Checklist:

  • Enforce minimum password strength (12+ characters, mixed character classes)
  • Hash passwords with bcrypt, Argon2, or scrypt — never MD5, SHA-1, or plain SHA-256
  • Invalidate session tokens on logout — do not just clear the client-side cookie
  • Rotate session tokens after authentication (session fixation prevention)
  • Implement account lockout after repeated failed attempts
  • Require multi-factor authentication for privileged accounts
  • Set session cookies with HttpOnly, Secure, and SameSite=Strict

5. Implement Authorisation at Every Layer

Authentication confirms who a user is. Authorisation controls what they can do. The two are separate, and missing authorisation checks are one of the most common vulnerability classes — OWASP calls it Broken Access Control and ranks it #1.

What to do:

  • Check authorisation server-side on every request — never rely on the UI hiding a button
  • Use a consistent, centralised authorisation check rather than scattered if statements
  • Apply the principle of least privilege — users should only access what they need
  • Test object-level authorisation: can user A access user B's records by changing an ID?
  • Deny by default — new endpoints should require explicit permission grants

6. Set Security Headers on Every Response

HTTP security headers are a low-effort, high-value defence. They instruct browsers to enforce security policies and protect against a range of attacks.

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Content-Security-Policy: default-src 'self'

These can be set in your web server configuration, CDN, or application middleware. Tools like securityheaders.com can audit your current headers.


7. Protect Against CSRF on State-Changing Requests

Cross-site request forgery (CSRF) tricks authenticated users into making unintended requests. It is most relevant for state-changing operations (POST, PUT, DELETE).

What to do:

  • Use the SameSite=Strict or SameSite=Lax cookie attribute — this is the most effective modern defence
  • For APIs, validate the Origin or Referer header against your expected domain
  • Use CSRF tokens for traditional server-rendered forms
  • Ensure state-changing endpoints do not respond to GET requests

8. Keep Dependencies Updated and Audited

Third-party libraries introduce vulnerabilities you did not write and may not know about. Dependency confusion and supply chain attacks are an increasing concern.

What to do:

  • Run npm audit, pip audit, or equivalent on every build
  • Integrate dependency scanning into your CI pipeline (Dependabot, Snyk, OWASP Dependency-Check)
  • Pin dependency versions and review changes in lock files during code review
  • Remove unused dependencies — they are attack surface with no benefit
  • Check new packages before adding them — verify publisher, download counts, and source

9. Never Store Secrets in Code or Version Control

API keys, database credentials, and signing secrets committed to a repository are a critical exposure — even in private repositories, and especially if the repository is ever made public or an account is compromised.

What to do:

  • Use environment variables or a secrets manager (AWS Secrets Manager, HashiCorp Vault, Doppler)
  • Add a .gitignore entry for .env files before the first commit
  • Scan your repository history for secrets using tools like TruffleHog or GitLeaks
  • Rotate any secret that has ever been committed — assume it is compromised
  • Use short-lived credentials where possible

10. Handle Errors Safely — Do Not Leak Stack Traces

Verbose error messages and stack traces in production responses tell attackers about your technology stack, file paths, and internal structure. This information directly aids further attack.

What to do:

  • Return generic error messages to users in production ("An error occurred")
  • Log full stack traces internally — never in API responses
  • Set NODE_ENV=production (or equivalent) to suppress framework debug output
  • Do not expose version numbers in error pages or headers
  • Test error paths — make sure your error handler does not accidentally return sensitive data

Summary

Practice Primary Risk Mitigated
Input validation Injection, XSS, logic flaws
Parameterised queries SQL injection
Output encoding XSS
Authentication hardening Account compromise
Authorisation checks Broken access control
Security headers XSS, clickjacking, sniffing
CSRF protection Cross-site request forgery
Dependency management Supply chain vulnerabilities
Secrets management Credential exposure
Safe error handling Information leakage

Security built in from the start is significantly cheaper to maintain than security bolted on after a penetration test finds a critical vulnerability. If your team would benefit from a security review of an existing application, or a secure development engagement for a new build, get in touch with NeedSec.

Need help with this area?

Get a quote to discuss a security assessment for your organisation.

Get a Quote