Security Best Practices for Web Developers
Web security is not optional. With increasing cyber threats, developers must build security into every layer of their applications. This guide covers essential practices you should implement today.
Password Security
Never Store Plain Text Passwords
Always hash passwords using modern algorithms like bcrypt, Argon2, or scrypt. These algorithms are designed to be slow, making brute-force attacks impractical.
// Good: Using bcrypt
const bcrypt = require('bcrypt');
const hash = await bcrypt.hash(password, 12);
Hashing Algorithm Comparison
Not all hashing algorithms are equal. Choosing the right one depends on your threat model and use case:
| Algorithm | Output Size | Speed | Use Case | Security Status |
|---|---|---|---|---|
| MD5 | 128-bit | Very fast | Checksums, non-security hashes | Broken for security |
| SHA-256 | 256-bit | Fast | Data integrity, digital signatures | Secure |
| bcrypt | 184-bit | Slow (tunable) | Password hashing | Secure |
| Argon2 | Configurable | Slow (tunable) | Password hashing (modern) | Recommended for new projects |
bcrypt and Argon2 are deliberately slow — this is a feature, not a bug. Each hash operation takes tens or hundreds of milliseconds, making large-scale brute-force attacks economically infeasible.
Understanding Password Entropy
Password strength can be measured mathematically using entropy: entropy = log2(charset_size^length). A password using lowercase letters (26 characters) with 8 characters has ~37.6 bits of entropy. A 16-character password mixing uppercase, lowercase, digits, and symbols (95 characters) has ~105 bits — exponentially harder to crack. This is why length matters more than complexity for most users. For a deeper dive into the math behind password strength, see our password entropy explained guide.
Use a Password Manager
Recommend that your users adopt a password manager. Human-chosen passwords tend to follow predictable patterns that attackers exploit with dictionary attacks. Password managers generate truly random strings and eliminate password reuse across services — one of the most common vectors for credential stuffing attacks.
Use Sufficient Salt Rounds
Salt rounds determine the computational cost. Higher is more secure but slower. 10-12 rounds is a good balance for most applications.
Input Validation
Validate on Both Client and Server
Client-side validation improves UX, but server-side validation is essential for security. Never trust client input.
Sanitize All User Input
Prevent injection attacks by sanitizing input:
- Use parameterized queries for SQL
- Escape HTML output to prevent XSS
- Validate file uploads strictly
Concrete Attack Examples
Understanding real attacks helps you defend against them. Consider a comment form that renders user input directly into HTML. An attacker submits:
<script>alert('xss')</script>
If the application renders this without escaping, the script executes in every visitor’s browser — stealing cookies, redirecting users, or injecting keyloggers. The fix: always encode output contextually. Use libraries like DOMPurify for HTML sanitization.
SQL injection is equally dangerous. In a login form, an attacker enters the username:
' OR 1=1 --
If the query is built with string concatenation ("SELECT * FROM users WHERE username='" + input + "'") this bypasses authentication entirely. The -- comments out the rest of the query. The fix: always use parameterized queries (also called prepared statements). Every major database library supports them:
// WRONG: String concatenation
db.query(`SELECT * FROM users WHERE username='${input}'`);
// RIGHT: Parameterized query
db.query('SELECT * FROM users WHERE username = $1', [input]);
Content Security Policy (CSP)
As defense-in-depth, deploy Content Security Policy headers. CSP tells the browser which sources of content are trusted, effectively blocking inline scripts and unauthorized resource loading. Even if an XSS vulnerability exists in your code, a strict CSP can prevent the injected script from executing. Start with Content-Security-Policy: default-src 'self' and gradually add exceptions as needed.
Hash Functions
Choosing the Right Hash
Different use cases require different hash functions:
| Use Case | Recommended |
|---|---|
| Passwords | bcrypt, Argon2 |
| Integrity | SHA-256 |
| Checksums | SHA-256, MD5 (non-security) |
| Fast hashing | BLAKE3 |
Understanding Hash Output and Collisions
MD5 produces a 128-bit (32 hex character) hash, while SHA-256 produces a 256-bit (64 hex character) hash. This difference matters: a larger output space means exponentially more possible hash values, making collisions far less likely. A collision occurs when two different inputs produce the same hash — an attacker who can manufacture collisions can forge digital signatures or tamper with verified data.
MD5 collisions can be generated in seconds on modern hardware. SHA-256 remains collision-resistant with no known practical attacks. This is why choosing the right algorithm for the right context is critical:
- Checksums and deduplication: MD5 is acceptable when security is not a concern
- Data integrity and signatures: SHA-256 provides strong collision resistance
- Password storage: bcrypt or Argon2, which add salt and deliberate slowness
HMAC for Message Authentication
When you need to verify both integrity and authenticity of a message, use HMAC (Hash-based Message Authentication Code). HMAC combines a hash function with a secret key, ensuring that only parties who know the key can generate or verify the tag. This is essential for API authentication, webhook verification, and secure token generation.
Never Use MD5 or SHA-1 for Security
MD5 and SHA-1 are broken for security purposes. Use SHA-256 or SHA-3 for cryptographic hashing.
HTTPS Everywhere
What TLS Actually Does
TLS (Transport Layer Security) provides three critical protections: encryption in transit (preventing eavesdropping), server authentication (proving you are talking to the real server, not an impostor), and data integrity (detecting any tampering during transmission). Without TLS, every piece of data between your users and your server — passwords, tokens, personal information — travels in plain text.
Always Use TLS
- Obtain certificates from trusted CAs (Let’s Encrypt is free and fully automated)
- Redirect HTTP to HTTPS
- Use HSTS headers
- Keep TLS versions updated
HSTS and Mixed Content
HTTP Strict Transport Security (HSTS) headers tell browsers to only connect via HTTPS, even if the user types http://. Set Strict-Transport-Security: max-age=31536000; includeSubDomains to enforce this for a full year across all subdomains. This prevents SSL stripping attacks where an attacker downgrades a connection to HTTP.
Watch out for mixed content warnings: if your HTTPS page loads images, scripts, or stylesheets over HTTP, browsers will block or warn about them. Audit your pages for hardcoded http:// URLs and use protocol-relative paths or enforce HTTPS for all resources.
Authentication
Implement Rate Limiting
Prevent brute-force attacks with rate limiting:
- Limit login attempts per IP
- Add delays after failed attempts
- Use CAPTCHA for suspicious activity
JWT Authentication Basics
JSON Web Tokens (JWT) provide a stateless authentication mechanism structured as header.payload.signature. The server signs the token with a secret key, and clients include it in subsequent requests. Because the token contains the user’s claims, the server does not need to look up session state on every request — making JWTs well-suited for distributed systems and microservices.
Always set short expiration times on access tokens (e.g., 15 minutes) and use refresh tokens for obtaining new access tokens. Store refresh tokens securely (httpOnly cookies, not localStorage) and implement token rotation so each refresh token can only be used once.
Multi-Factor Authentication (MFA)
MFA is no longer optional for any serious application. Requiring a second factor — TOTP codes (Google Authenticator), hardware keys (YubiKey), or push notifications — dramatically reduces the impact of compromised passwords. Even if an attacker obtains valid credentials, they cannot authenticate without the second factor.
Session Fixation Prevention
Session fixation attacks occur when an attacker sets a known session ID before the user authenticates. After login, the attacker uses that same session ID to hijack the authenticated session. Prevent this by always regenerating the session ID after successful authentication and invalidating the old one.
Use Secure Session Management
- Generate cryptographically random session IDs
- Set secure and httpOnly flags on cookies
- Implement session timeout
- Invalidate sessions on logout
Security Headers Checklist
Deploying the right HTTP response headers is one of the most effective and low-effort ways to harden your application. Here is a quick-reference table of essential security headers:
| Header | Purpose | Example Value |
|---|---|---|
| Content-Security-Policy | Prevent XSS and data injection | default-src 'self' |
| Strict-Transport-Security | Force HTTPS connections | max-age=31536000; includeSubDomains |
| X-Content-Type-Options | Prevent MIME type sniffing | nosniff |
| X-Frame-Options | Prevent clickjacking | DENY |
| Referrer-Policy | Control referrer information | strict-origin-when-cross-origin |
These headers can be set at the web server level (Nginx, Apache), at the CDN/edge level (Cloudflare, Vercel), or within your application framework. Test your headers using tools like securityheaders.com. Aim for an A+ rating — most of these headers are a single line of configuration and cost nothing to deploy.
Using Our Security Tools
Explore our security tools to help with your development:
- MD5 Hash Generator - For checksums and legacy systems
- UUID Generator - For secure random identifiers
- Random Password Generator - For generating strong passwords
For a broader overview of how encoding, hashing, and conversion tools fit into your development workflow, see our Essential Developer Tools Guide.
Frequently Asked Questions
What is the most common web security vulnerability?
Cross-Site Scripting (XSS) remains the most prevalent web vulnerability according to OWASP. It occurs when applications include untrusted data in web pages without proper validation. Prevent XSS by sanitizing all user input, using Content Security Policy headers, and encoding output based on context (HTML, JavaScript, URL, or CSS).
Is MD5 still safe for password hashing?
No — MD5 should never be used for password hashing. It is computationally fast, making it vulnerable to brute-force and rainbow table attacks. Modern GPUs can compute billions of MD5 hashes per second. Use bcrypt, scrypt, or Argon2 instead, which are deliberately slow and include built-in salting to resist attacks.
How long should a secure password be in 2026?
A minimum of 12 characters is recommended, but 16+ characters provides significantly stronger protection. Length matters more than complexity — a 20-character passphrase like “correct-horse-battery-staple” is stronger than a short complex password like “P@ss1!”. Enable multi-factor authentication (MFA) regardless of password length for critical accounts.
What is the difference between encryption and hashing?
Encryption is reversible — you can decrypt data back to its original form using a key. Hashing is one-way — you cannot recover the original data from a hash. Use encryption for data you need to retrieve (like stored user data), and hashing for data you only need to verify (like passwords and checksums).
Should I implement my own authentication system?
No — building authentication from scratch is risky and error-prone. Use battle-tested frameworks and services like Auth0, Firebase Auth, or Supabase Auth. These handle password hashing, session management, token rotation, MFA, and brute-force protection. Focus your development time on your application’s unique features instead.
Conclusion
Security is an ongoing process, not a one-time task. Stay updated with the latest vulnerabilities, regularly audit your code, and follow the principle of least privilege. Your users trust you with their data - honor that trust with robust security practices.