BlogSecurity

OWASP Secure Coding Practices

A comprehensive guide to OWASP secure coding guidelines. Learn how to write secure code and prevent common vulnerabilities.

22 min readUpdated December 2025

What is OWASP?

The Open Web Application Security Project (OWASP) is a nonprofit foundation that works to improve the security of software. OWASP provides free, open resources including tools, documentation, and community-driven projects.

Key OWASP Resources

OWASP Top 10: Most critical web app security risks
ASVS: Application Security Verification Standard
SAMM: Software Assurance Maturity Model
Cheat Sheets: Quick reference security guides

Secure Coding Practices

Input Validation

Validate all input data from untrusted sources. Never trust user input.

Guidelines:

  • Validate input on the server side (client-side is not enough)
  • Use allowlisting over denylisting where possible
  • Validate data type, length, format, and range
  • Sanitize input before use in queries, commands, or output
  • Reject invalid input rather than trying to fix it
  • Use parameterized queries for database operations

Example:

Bad:
const email = req.body.email; // No validation
Good:
const { email } = req.body;
if (!email || !validator.isEmail(email)) {
  throw new Error('Invalid email');
}

Authentication & Password Management

Implement robust authentication to verify user identity.

Guidelines:

  • Hash passwords using strong algorithms (bcrypt, Argon2, PBKDF2)
  • Enforce password complexity requirements
  • Implement account lockout after failed attempts
  • Use multi-factor authentication (MFA) for sensitive operations
  • Don't reveal if username or password was incorrect
  • Rotate credentials regularly and after compromise

Example:

Bad:
const hash = md5(password); // Weak hashing
Good:
const hash = await bcrypt.hash(password, 12);
const isValid = await bcrypt.compare(input, hash);

Session Management

Protect session tokens and maintain secure session lifecycle.

Guidelines:

  • Generate unpredictable session IDs with sufficient entropy
  • Set Secure, HttpOnly, and SameSite cookie attributes
  • Regenerate session ID after authentication
  • Implement session timeout (idle and absolute)
  • Invalidate sessions on logout
  • Don't expose session IDs in URLs

Example:

Bad:
res.cookie("session", sessionId); // Missing security flags
Good:
res.cookie("session", sessionId, {
  httpOnly: true,
  secure: true,
  sameSite: 'strict',
  maxAge: 3600000
});

Access Control

Restrict access to resources based on user authorization.

Guidelines:

  • Deny by default - require explicit grants
  • Enforce access control on every request server-side
  • Use role-based access control (RBAC)
  • Check authorization for every resource access
  • Log access control failures
  • Invalidate tokens/sessions when permissions change

Example:

Bad:
// Only checks authentication, not authorization
if (user) { return resource; }
Good:
// Check both authentication and authorization
if (!user) throw new AuthError();
if (!user.hasPermission('resource:read')) {
  throw new ForbiddenError();
}

Cryptographic Practices

Use strong cryptography to protect sensitive data.

Guidelines:

  • Use established cryptographic algorithms (AES-256, RSA-2048+)
  • Don't create your own cryptographic functions
  • Store cryptographic keys securely (HSM, secrets manager)
  • Use TLS 1.2+ for data in transit
  • Generate random numbers using cryptographic PRNG
  • Regularly rotate encryption keys

Example:

Bad:
const key = "hardcoded-secret-key"; // Hardcoded key
Good:
const key = process.env.ENCRYPTION_KEY;
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);

Error Handling & Logging

Handle errors securely without revealing sensitive information.

Guidelines:

  • Don't expose stack traces or internal errors to users
  • Log security events with sufficient detail
  • Don't log sensitive data (passwords, tokens, PII)
  • Use centralized logging and monitoring
  • Implement alerts for security events
  • Protect log files from tampering

Example:

Bad:
res.status(500).json({ error: error.stack }); // Exposes internals
Good:
logger.error('Database error', { errorId, userId });
res.status(500).json({
  error: 'An error occurred',
  errorId
});

Data Protection

Protect sensitive data at rest and in transit.

Guidelines:

  • Classify data by sensitivity level
  • Encrypt sensitive data at rest
  • Use TLS for all data in transit
  • Minimize data collection and retention
  • Implement data backup and recovery
  • Apply privacy regulations (GDPR, CCPA)

Example:

Bad:
db.save({ ssn: user.ssn }); // Unencrypted PII
Good:
const encryptedSSN = encrypt(user.ssn);
db.save({ ssn: encryptedSSN });

Communication Security

Secure all network communications.

Guidelines:

  • Use TLS 1.2+ for all connections
  • Implement HSTS (HTTP Strict Transport Security)
  • Validate TLS certificates properly
  • Use secure cipher suites
  • Disable deprecated protocols (SSL, TLS 1.0/1.1)
  • Pin certificates for mobile apps

Example:

Bad:
fetch("http://api.example.com/data"); // Insecure HTTP
Good:
fetch("https://api.example.com/data", {
  headers: { 'Strict-Transport-Security': 'max-age=31536000' }
});

System Configuration

Secure system and application configuration.

Guidelines:

  • Remove unnecessary features and files
  • Change default passwords and settings
  • Keep software and dependencies updated
  • Use least privilege for services
  • Disable directory listing
  • Implement security headers (CSP, X-Frame-Options)

Example:

Bad:
// No security headers
Good:
app.use(helmet()); // Adds security headers
app.use(helmet.contentSecurityPolicy({
  directives: { defaultSrc: ["'self'"] }
}));

Quick Security Checklist

Do

  • Validate all input server-side
  • Use parameterized queries
  • Hash passwords with bcrypt/Argon2
  • Encrypt sensitive data
  • Use HTTPS everywhere
  • Implement proper access control
  • Log security events
  • Keep dependencies updated

Don't

  • Trust user input
  • Concatenate SQL queries
  • Store passwords in plain text
  • Hardcode secrets in code
  • Expose stack traces to users
  • Use deprecated crypto
  • Log sensitive data
  • Ignore security warnings

How These Practices Map to the OWASP Top 10

The secure coding practices above are not arbitrary — each one directly addresses categories in the OWASP Top 10 (2021 edition), the industry's consensus list of the most critical web application risks. Understanding the mapping helps teams justify security work to stakeholders and prioritize which practices to adopt first.

  • A01 Broken Access Control: the number one risk, covered by the access control practices — deny by default, server-side enforcement on every request, and authorization checks per resource (CWE-862, CWE-863, CWE-639 for insecure direct object references).
  • A02 Cryptographic Failures: addressed by the cryptographic and data protection practices — modern algorithms, TLS 1.2+, no hardcoded keys (CWE-327, CWE-321).
  • A03 Injection: mitigated by input validation and parameterized queries. Injection now includes XSS in the 2021 taxonomy (CWE-89, CWE-79, CWE-78).
  • A05 Security Misconfiguration: covered by system configuration practices — security headers, hardened defaults, removed debug features (CWE-16).
  • A07 Identification and Authentication Failures: addressed by the authentication and session management sections — strong hashing, MFA, session regeneration (CWE-287, CWE-384).
  • A09 Security Logging and Monitoring Failures: covered by error handling and logging practices — auditable events without sensitive data leakage (CWE-532, CWE-778).

Two categories deserve extra attention because coding practices alone cannot fully address them. A06 (Vulnerable and Outdated Components) requires continuous dependency scanning, since a perfectly written application can be compromised through a library it imports. And A08 (Software and Data Integrity Failures) extends into your build pipeline — unsigned artifacts, untrusted plugins, and insecure deserialization all fall here, which is why supply-chain controls and CI/CD hardening belong in the same conversation as secure coding.

Making Secure Coding Stick: Process Over Heroics

A checklist that lives in a wiki changes nothing. Secure coding practices only survive contact with deadlines when they are embedded into the development workflow itself. OWASP's SAMM (Software Assurance Maturity Model) describes this as moving from ad-hoc security to institutionalized practice, and the pattern that works looks broadly the same everywhere:

  • Threat model at design time: a 30-minute STRIDE-style session on new features catches architectural flaws — missing authorization boundaries, unencrypted data flows — that no code scanner will ever find.
  • Codify rules as automation: every practice in this guide has a corresponding SAST or linter rule. Parameterized query enforcement, weak-hash detection, missing security headers, and hardcoded secrets can all be flagged automatically on every pull request, turning the guidelines from tribal knowledge into a quality gate. Platforms such as TigerGate ship OWASP-aligned rule packs so teams do not have to author these checks from scratch.
  • Review with intent: add two or three security-specific questions to your PR template — does this change handle untrusted input, touch authentication, or add a dependency? Focused questions beat generic "looks good" reviews.
  • Verify against ASVS: the OWASP Application Security Verification Standard defines three levels of rigor. Level 1 (basic, fully automatable) suits most applications; Level 2 fits anything handling sensitive data; Level 3 is for high-value targets like payment or healthcare systems. Pick a level per application tier and audit against it annually.
  • Train with real findings: the most effective security training uses vulnerabilities found in your own codebase, not abstract examples. A quarterly review of the top recurring finding classes tells you exactly what to teach.

Common Mistakes Even Experienced Teams Make

Some anti-patterns persist even in mature organizations. Validating input on the client but not the server remains widespread because it "works" in normal use and fails only under attack. Teams frequently sanitize input at the entry point but forget context-specific output encoding — data that is safe in HTML can still break out of a JavaScript string or a URL parameter. Access control checks often exist on the UI route but not the underlying API endpoint, which is exactly the gap BOLA/IDOR attacks exploit. And secrets management commonly regresses during incidents: a credential pasted into a debug log or an environment file committed "temporarily" outlives the incident by years. Automated scanning catches the mechanical cases; the architectural cases require review discipline.

Finally, remember that secure coding is a floor, not a ceiling. These practices prevent the introduction of common weaknesses, but defense in depth still matters: runtime monitoring detects exploitation of the flaws that slip through, dependency scanning covers code you did not write, and least-privilege infrastructure limits the blast radius when something fails. Treat the OWASP guidelines as the foundation of a layered program rather than its entirety.

Automate Security Checks

TigerGate automatically scans your code for OWASP vulnerabilities. Get real-time feedback on security issues before they reach production.

Start Free Security Scan