How to Write Secure Code
Secure code isn’t some magic spell you sprinkle on top at the end. It’s the habit of writing software in a way that helps protect it from vulnerabilities. If you build those habits during development—especially around input validation, encryption, and OWASP secure coding practices—you make it harder for attackers to find openings in your app.
Here’s a workflow you can use on real code.
What It Means to Write Secure Code
When people say “secure coding,” they’re usually pointing at one goal: protect your software from vulnerabilities.
That typically means you:
- Treat some data as dangerous until it’s proven safe (input validation).
- Protect sensitive information so people can’t read it while it’s moving across the network (encryption and TLS).
- Follow guidance that’s already been tested in the real world instead of improvising (OWASP secure coding practices).
- Build security into how you work, not just into the code (team culture and best practices).
A simple way to think about it: security mostly comes down to decisions. Where does the data come from? What shape should it have? What happens if it’s wrong? Is it safe to send over the network? Those choices show up everywhere in your code.
Start With Data: Identify Trusted vs Untrusted Sources
Before you validate anything, you need to know where the data is coming from.
A common quick-reference approach is to split inputs into two buckets:
- Trusted data: data you can reasonably assume comes from a safe place (for example, data you generate internally and didn’t expose to the outside world).
- Untrusted data: data that comes from outside your control. That includes user input, request parameters, and any external systems you don’t fully control.
This is why untrusted data can’t be treated as safe by default. You earn trust by handling it carefully—mostly through input validation and safe processing.
A good “do this next” step:
- List the inputs your code accepts (UI fields, API request fields, file uploads, headers, query params).
- For each one, mark it as trusted or untrusted.
- For anything untrusted, plan to validate and sanitize before you use it.
Validate Inputs to Reduce Vulnerabilities
Input validation is repeated so often for a reason: many security bugs start with code accepting data that wasn’t what it expected.
The core idea is straightforward: only accept inputs that match what your code expects.
In practice, that usually means:
- Check type: is it a number when you expect a number?
- Check format: does it look like a date, email, or other expected format?
- Check length: are strings within a reasonable size?
- Check range: are numbers within acceptable bounds?
- Fail fast: if the input is wrong, stop and return a safe error.
Also, don’t validate only at the UI (or only at one boundary). Inputs can arrive from multiple paths. Validate any time untrusted data crosses into your business logic.
One more practical point: validation helps with more than security. If you enforce constraints early, your code downstream doesn’t have to guess what it’s getting.
Protect Sensitive Data With Encryption
Encryption is how you protect sensitive information so that storage and processing don’t expose it to outsiders.
The mindset:
- If the data is sensitive (credentials, tokens, private user info, other secrets), treat it as likely to leak at some point.
- Use encryption so that even if someone gets access to the raw data, it’s much harder for them to read.
Where this shows up in code:
- Encrypt before you persist sensitive data.
- Keep encryption and decryption logic clear and centralized so it’s less likely to be implemented inconsistently across the app.
- Make sure you’re not accidentally logging sensitive values—logging is often where “we’ll encrypt it” plans fail.
You don’t need to overcomplicate it. Start with the rule: sensitive data should be encrypted in storage and other sensitive handling paths, not left as plain text.
Secure Data in Transit With TLS
Even with encryption at rest, attackers can still try to steal data while it’s traveling across the network.
That’s why TLS matters. TLS is the standard way to secure communications over the internet. It’s the “protect the connection” layer. The common guidance is:
- Encrypt transmission of sensitive information using TLS
- Use TLS as part of protecting data in transit
In developer terms, “use TLS” usually means:
- Make sure clients and servers communicate using TLS instead of plain HTTP.
- Don’t accept or silently allow insecure connection downgrades.
- Treat anything sent over the network as potentially visible to outsiders unless TLS is in place.
If your app sends sensitive data—login sessions, personal info, payment-related details, tokens—then the transmission path should be protected with TLS.
Follow OWASP Secure Coding Practices (and Use Them as a Checklist)
OWASP secure coding practices are a clear set of known-good guidance for building safer software. You don’t have to memorize it all at once.
What you want is a checklist mindset that matches the repeated themes: trusted vs untrusted data, input validation, encryption, and TLS.
Here’s a workflow you can reuse for most code changes:
The OWASP-style checklist you can apply quickly
- Classify data
- Identify trusted vs untrusted inputs.
- Validate all untrusted input
- Enforce expected type/format/length/range.
- Reject bad data early.
- Prevent unsafe use of input
- Make sure validated input is used safely in your code paths.
- Encrypt sensitive data
- Use encryption for sensitive info where it needs protection.
- Protect data in transit
- Use TLS when sensitive data is transmitted over the network.
- Keep secrets out of places they don’t belong
- Don’t log secrets. Don’t hardcode them where they’ll leak.
- Follow secure coding standards your team agrees on
- If your process mentions secure coding standards (for example, NIST), use them as a baseline for what your org expects in code and review.
If you want one practical rule: every security decision should tie back to input handling or encryption/TLS. If it doesn’t, you probably don’t have a real reason yet.
Adopt Secure Development Best Practices and Team Culture
Even strong code can get undone by how a team works. That’s why secure coding guidance often includes more than technical steps—it also covers software development best practices and a change in day-to-day attitude and culture.
What “culture” looks like in practice:
- Security isn’t only the responsibility of the security team.
- Developers review security impacts as part of normal work.
- Secure patterns are expected by default, not only when someone asks.
Concrete best practices that support this:
- Make security part of your definition of done (not a last-minute “extra”).
- Encourage developers to ask: What data is untrusted here? and Should this be encrypted and sent via TLS?
- Keep a simple shared checklist (like the one above) so review doesn’t turn into guesswork.
- Treat security bugs like real bugs that need real fixes, not “edge cases.”
When the team culture is right, secure code becomes the easiest path.
Common Secure Coding Gaps to Watch For
Most vulnerabilities don’t appear out of nowhere. They tend to come from repeat patterns of mistakes. These map directly to the themes used throughout this article:
- Assuming inputs are safe
- If it’s from outside your trust boundary, assume it’s hostile until validated.
- Inconsistent validation
- Validate in one place, then reuse the same data elsewhere without checks.
- Over-trusting “frontend validation”
- Client-side checks can be bypassed. Validate at the server (or in the real trust boundary).
- Missing encryption for sensitive data
- Storing or handling sensitive data in plain text when it should be protected with encryption.
- Not using TLS for sensitive traffic
- Sending sensitive data over insecure channels so it can be intercepted.
- Security checks scattered and hard to review
- If validation and encryption happen in five different ways, it’s easier to miss a path or make a mistake.
- Not thinking in terms of trusted vs untrusted
- If your code doesn’t clearly label trust, your validations will be uneven.
If you do code review, keep these in mind as a quick “spot the bug” list.
Secure Code Example: Putting the Checklist Into Practice
Below is a high-level secure code example that demonstrates the workflow without depending on specific libraries. The goal is to mirror the same elements: trusted vs untrusted data, input validation, encryption, and TLS.
Example scenario
You have an API endpoint that accepts an identifier from a user, fetches a user record, and returns sensitive fields. Some fields must be protected, and transmission must be secured.
High-level pseudocode
- Classify the input
- Treat the incoming identifier as untrusted.
- Validate the input
- Check the identifier type, format, and length.
- Reject invalid requests with a safe error.
- Handle sensitive data
- Before storing or returning sensitive fields, protect sensitive data with encryption where appropriate.
- Rely on secure transport
- Ensure the service is configured to use TLS for incoming and outgoing requests carrying sensitive info.
In plain terms, it might look like this:
- Receive request with `userId` (untrusted)
- If `userId` fails validation rules:
- return error (don’t proceed)
- Fetch user record
- For sensitive fields:
- keep them encrypted at rest (if stored)
- decrypt only when you must use them
- Return response over the connection protected by TLS
The takeaway isn’t the syntax. It’s the sequence:
- validate untrusted inputs
- apply encryption for sensitive data
- ensure TLS for sensitive transmission
If you want to turn this into a real code review standard, ask the same four questions for every endpoint:
- What inputs are untrusted?
- What checks enforce input validation?
- Where does encryption apply to sensitive data?
- Is sensitive transmission using TLS?
How to Keep Improving: Code Review and Ongoing Secure Coding
Security isn’t a one-time task. It’s a loop.
Use the checklist as part of your workflow, then improve based on what you find:
- Run through the trusted/untrusted classification for every change.
- Re-check input validation boundaries, especially where data crosses into business logic.
- Confirm where encryption is used for sensitive data and where it isn’t.
- Verify that TLS is used for sensitive transmission paths.
- Keep OWASP secure coding practices in your review mindset as your shared north star.
Then don’t stop there. Iterate. If a reviewer spots a validation miss, update the team checklist or add a short note about how you handle that data type next time. If you see repeated problems around the same trust boundary, tighten the standard.
Use the checklist in this article in your next code review, and iterate based on what you see in your inputs and data handling.