Software Development Trending

Secure Coding Best Practices Every Developer Should Follow

Practical secure coding habits for input handling, secrets, authentication, dependencies and logging, with short code examples you can apply on your next pull request.

Quick answer

The most important secure coding practices are to treat all input as untrusted, use parameterized queries and output encoding, keep secrets out of source code, hash passwords with a modern algorithm like Argon2id, check authorization on every request, keep dependencies patched and log security events without logging sensitive data. Automate checks in CI so they happen every time.

Secure Coding Best Practices Every Developer Should Follow

Photo: Unsplash

On this page

Most security breaches in web applications don’t come from genius hackers finding exotic flaws. They come from ordinary mistakes: a query built with string concatenation, an API key committed to a public repo, a missing permission check on one endpoint, or a library that hasn’t been updated in 2 years.

The good news is that those mistakes are preventable with habits, not heroics. This guide covers the secure coding practices that prevent the most common vulnerabilities, with short code examples in Python and JavaScript. It’s written for working developers and the managers who set their team’s standards.

The mindset: assume every input is hostile

Secure code starts from one assumption: anything that comes from outside your code can be malicious. That includes form fields, URL parameters, HTTP headers, cookies, uploaded files, webhook payloads, data from third-party APIs and even data from your own database if users wrote it.

From that assumption follow 3 principles:

  • Validate on the way in. Check that data has the type, length, format and range you expect.
  • Encode on the way out. Make sure data can’t be interpreted as code wherever it ends up: SQL, HTML, shell commands or logs.
  • Grant the least privilege. Every user, service and database account gets only the access it needs.

These ideas underpin most of the risks in the OWASP Top 10, the most widely used awareness list of web application risks.

Input handling: validation and safe queries

Validate with allow-lists

Define what good input looks like and reject everything else. Allow-lists (only these values or patterns are accepted) are far stronger than deny-lists (block these known-bad strings), because attackers are creative about getting around deny-lists.

```python ALLOWED_SORT = {“name”, “created_at”, “price”}

def get_sort_field(value: str) -> str: if value not in ALLOWED_SORT: raise ValueError(“Invalid sort field”) return value ```

Always validate on the server. Browser-side checks improve user experience but anyone can bypass them.

Use parameterized queries, every time

SQL injection is old, well understood and still common. The OWASP SQL Injection Prevention Cheat Sheet lists prepared statements with parameterized queries as the first defense and strongly discourages relying on escaping user input.

```python

Unsafe: user input becomes part of the SQL

cur.execute(f”SELECT * FROM users WHERE email = ‘{email}’”)

Safe: the driver sends the value separately from the query

cur.execute(“SELECT * FROM users WHERE email = %s”, (email,)) ```

javascript // Safe with node-postgres const result = await db.query( "SELECT * FROM users WHERE email = $1", [email] );

Most ORMs parameterize by default, but watch for “raw query” escape hatches. Parts of a query that can’t be parameterized, such as table or column names, should come from an allow-list like the sort example above.

Encode output for its context

Cross-site scripting happens when user input is rendered as HTML or JavaScript. Modern frameworks such as React, Angular and Vue escape output by default. The danger lies in the bypasses: dangerouslySetInnerHTML in React, v-html in Vue, or building HTML strings by hand. If you must render user-supplied HTML, run it through a well-maintained sanitizer library.

The same rule applies to shell commands. Avoid passing user input to a shell. If you must call an external program, pass arguments as a list rather than a single string.

Secrets: keep them out of code

API keys, database passwords and signing keys committed to source control are one of the most common and most damaging mistakes. Once a secret lands in Git history, assume it’s exposed, even if you delete it in the next commit.

```python import os

Bad: hard-coded secret

PAYMENT_API_KEY = “sk_live_abc123…”

Better: read from the environment or a secrets manager at runtime

PAYMENT_API_KEY = os.environ[“PAYMENT_API_KEY”] ```

Practical habits:

  • Store secrets in a secrets manager (cloud key vaults or a dedicated tool) and inject them at runtime.
  • Add .env files to .gitignore and commit a .env.example with placeholder values instead.
  • Turn on secret scanning in your repository host and add a pre-commit hook that blocks likely secrets.
  • If a secret leaks, rotate it immediately. Deleting the commit is not enough.
  • Use separate credentials for development, staging and production, each with the minimum permissions needed.

Key takeaway: If a secret has ever been in your repository, treat it as public and rotate it. Prevention is cheap; cleanup after a leak is not.

Authentication and authorization

Store passwords properly

Never store passwords in plain text or with a fast hash like MD5 or SHA-256. The OWASP Password Storage Cheat Sheet recommends Argon2id first, scrypt if Argon2id isn’t available, bcrypt for legacy systems and PBKDF2 where FIPS-140 compliance is required.

```python from argon2 import PasswordHasher # argon2-cffi package

ph = PasswordHasher() stored_hash = ph.hash(password) # at sign-up ph.verify(stored_hash, attempt) # at log-in, raises on mismatch ```

Better still, reduce how much you depend on passwords at all with multi-factor authentication or passkeys.

Check authorization on every request

Broken access control, where a user can reach data or actions they shouldn’t, is a top web risk. The classic bug: an endpoint like /invoices/1042 that checks the user is logged in but not that invoice 1042 belongs to them.

javascript // Check ownership, not just authentication const invoice = await Invoice.findById(req.params.id); if (!invoice || invoice.accountId !== req.user.accountId) { return res.status(404).send("Not found"); }

Enforce authorization on the server, deny by default and centralize the logic so it isn’t re-implemented (and forgotten) in each route. These are the same ideas behind zero trust security.

Dependencies: your code is mostly other people’s code

A typical application pulls in hundreds of open-source packages, directly and indirectly. A vulnerability in any of them is a vulnerability in your app, and attackers increasingly target the software supply chain directly.

Habit Why it matters Example tools
Commit lockfiles Builds use the exact versions you tested package-lock.json, poetry.lock
Scan for known vulnerabilities Flags packages with published flaws npm audit, pip-audit, Dependabot, OWASP Dependency-Check
Update on a schedule Small, regular updates are easier than big jumps Automated update pull requests
Vet new packages Typosquatted and abandoned packages are common attack routes Check maintainers, activity and download history
Generate an SBOM Lets you answer “are we affected?” fast when a new flaw is announced CycloneDX or SPDX tooling
Remove unused packages Less code means less attack surface Dependency analysis tools

Logging and error handling

Good logs help you detect and investigate attacks. Bad logs leak the data you’re trying to protect.

Log security events: failed and successful logins, password resets, permission changes, access-denied errors, input validation failures and admin actions. Include a timestamp, user or account ID, source IP and what happened.

Don’t log sensitive data. The OWASP Logging Cheat Sheet lists data to exclude, including passwords, session identifiers, access tokens, database connection strings, encryption keys, payment card data and sensitive personal data such as health information.

```python

Bad

logger.info(f”Login for {email} with password {password}”)

Better

logger.info(“login_success”, extra={“user_id”: user.id, “ip”: client_ip}) ```

Prevent log injection. Strip or encode carriage returns, line feeds and delimiters in user-supplied values so attackers can’t forge fake log entries.

Fail safely. Show users a generic error message and keep stack traces, SQL errors and internal paths in server logs only. Detailed errors in the browser hand attackers a map of your system.

Build security into your workflow

Individual habits slip under deadline pressure. Automation doesn’t. Moving security checks into the development pipeline is the core idea of DevSecOps.

NIST’s Secure Software Development Framework (SP 800-218, version 1.1, published February 2022) gives a widely referenced set of high-level practices for building security into any development life cycle, and it underpins US federal expectations for software suppliers. NIST released a draft update, version 1.2, in December 2025, so check NIST’s site for the current status.

In practice, that means:

  • Security checks in CI: static analysis (SAST), dependency scanning and secret scanning on every pull request.
  • Security in code review: add input handling, authorization and secrets to your reviewer checklist. Our guide on how to do code reviews includes one.
  • Security tests: write tests that try unauthorized access and malicious input, not just the happy path.
  • Threat modeling for big changes: before building a new feature, spend 30 minutes asking what could go wrong and who might abuse it.
  • Regular testing: schedule vulnerability scans and periodic penetration tests for important applications.

Where to start: a 7-step plan

  1. Turn on secret scanning in your repository host today and rotate anything it finds.
  2. Add dependency scanning with automated update pull requests.
  3. Search your codebase for string-built queries and convert them to parameterized queries.
  4. Audit authorization on your most sensitive endpoints: billing, user data, admin functions.
  5. Check your password storage and migrate to Argon2id or another recommended algorithm on the user’s next login.
  6. Review your logs for passwords, tokens and personal data, and add redaction.
  7. Add a SAST tool to CI and fix high-severity findings first.

Conclusion

Secure coding is mostly a set of repeatable habits: validate input, parameterize queries, encode output, keep secrets out of code, check authorization every time, keep dependencies current and log carefully. None of these are hard on their own. The challenge is doing them consistently, which is why the best teams write them into checklists, code reviews and automated pipeline checks rather than relying on memory.

Sources

Questions readers ask

Frequently asked questions

What are secure coding practices?

Secure coding practices are habits and rules that stop common vulnerabilities from getting into software. The core ones are validating all input, using parameterized queries, encoding output, keeping secrets out of source code, hashing passwords with a modern algorithm, checking authorization on every request, keeping dependencies patched and logging security events without exposing sensitive data.

How do I prevent SQL injection?

Use parameterized queries or prepared statements for every database query, so user input is sent separately from the SQL command. OWASP lists this as the primary defense and strongly discourages relying on escaping. For query parts that can't be parameterized, such as column names for sorting, accept only values from a fixed allow-list.

Where should I store API keys and passwords in my code?

Not in your code at all. Store secrets in a secrets manager or environment variables and load them at runtime. Keep .env files out of version control, turn on secret scanning in your repository and use separate credentials for each environment. If a secret is ever committed, rotate it right away, because it stays in Git history.

What is the best way to hash passwords?

OWASP currently recommends Argon2id as the first choice, scrypt when Argon2id isn't available, bcrypt only for legacy systems and PBKDF2 when FIPS-140 compliance is required. Never use plain text, encryption or fast hashes like MD5 or SHA-256 for passwords. Use a well-maintained library rather than writing your own hashing code.

What should never be written to application logs?

Never log passwords, session IDs, access tokens, API keys, encryption keys, database connection strings, payment card data or sensitive personal data such as health information. Log user IDs and event types instead. Also sanitize line breaks and delimiters in user-supplied values so attackers can't forge fake log entries.

Do small development teams need secure coding standards?

Yes. Attackers scan the internet automatically and don't care about company size. Small teams benefit most from free or low-cost automation: secret scanning, dependency alerts, a linter with security rules and a short review checklist. A one-page standard that everyone follows is better than a long policy nobody reads.

What is the NIST Secure Software Development Framework?

The SSDF, published as NIST SP 800-218, is a set of high-level practices for building security into any software development life cycle, covering preparing the organization, protecting software, producing well-secured software and responding to vulnerabilities. Version 1.1 was published in February 2022, and NIST released a draft version 1.2 in December 2025.

Reader reviews

Was this guide useful

No reviews yet. Share your experience and help other readers.

Write a review
Your rating
Never shown publicly

Reviews are checked before they appear. We never show your email. Please review only places or services you have used.

Manishdai
Written by

Manishdai

Manishdai writes about the technology that is reshaping every industry — from AI agents and cloud platforms to healthtech, fintech and the IT companies building it all across the United States.