Skip to content
Secure Coding Practices — A Beginner's Guide to Writing Secure Code

Secure Coding Practices — A Beginner's Guide to Writing Secure Code

DodaTech Updated Jun 7, 2026 12 min read

Secure coding is the practice of writing software that is resistant to security vulnerabilities by applying defensive programming techniques — validating input, encoding output, managing authentication securely, and avoiding common weaknesses from the start.

What You’ll Learn

By the end of this tutorial, you’ll understand the OWASP Application Security Verification Standard (ASVS), implement secure input validation and output encoding, follow authentication and session management best practices, and use SAST tools to catch vulnerabilities during development.

Why Secure Coding Matters

Over 70% of security vulnerabilities originate in application code, not infrastructure. Writing secure code from the start costs 10x less than fixing vulnerabilities after deployment. At DodaTech, Durga Antivirus Pro undergoes rigorous secure code reviews before every release.

Secure Coding Learning Path

    flowchart LR
  A[Web Security] --> B[DevSecOps]
  B --> C[Secure Coding]
  C --> D{You Are Here}
  style D fill:#f90,color:#fff
  
Prerequisites: Cyber Security basics and proficiency in at least one programming language (examples use Python and JavaScript).

What Is Secure Coding? (The “Why” First)

Think of secure coding like defensive driving. You don’t drive assuming everyone follows the rules — you anticipate that someone might run a red light, merge without signaling, or brake suddenly. Secure coding is the same: you don’t assume input is safe, users are authenticated, or third-party code is secure.

OWASP ASVS Overview

The OWASP Application Security Verification Standard defines four levels of security verification:

LevelDescriptionWhen to Use
L1Automated verificationAll applications (minimum)
L2Manual + automatedApplications handling sensitive data
L3In-depth verificationCritical applications (finance, healthcare, defense)

Core Secure Coding Principles

1. Input Validation — Never Trust User Input

Validate everything that comes from outside your application:

# input_validation.py — Secure input validation examples
import re
from typing import Optional

class InputValidator:
    """Demonstrates secure input validation patterns."""

    @staticmethod
    def validate_email(email: str) -> Optional[str]:
        """Validate and normalize an email address."""
        if not email or len(email) > 254:
            return None
        email = email.strip().lower()
        pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        if re.match(pattern, email):
            return email
        return None

    @staticmethod
    def validate_username(username: str) -> Optional[str]:
        """Validate username: alphanumeric, 3-30 chars."""
        if not username:
            return None
        username = username.strip()
        if re.match(r'^[a-zA-Z0-9_]{3,30}$', username):
            return username
        return None

    @staticmethod
    def sanitize_filename(filename: str) -> str:
        """Remove dangerous characters from filenames."""
        # Remove path traversal attempts
        safe = filename.replace('..', '')
        # Keep only safe characters
        safe = re.sub(r'[^\w\-\.]', '_', safe)
        # Limit length
        return safe[:255]

    @staticmethod
    def validate_integer(value: str, min_val: int, max_val: int) -> Optional[int]:
        """Safely parse and validate an integer."""
        try:
            num = int(value)
            if min_val <= num <= max_val:
                return num
            return None
        except (ValueError, TypeError):
            return None

# Example usage
validator = InputValidator()
print(validator.validate_email("user@example.com"))    # user@example.com
print(validator.validate_email("not-an-email"))        # None
print(validator.sanitize_filename("../../etc/passwd"))  # etc_passwd

Validation rules:

  • Whitelist — define what’s allowed (safer than blacklist)
  • Length limits — prevent buffer overflow, DoS
  • Type checking — integers are integers, not strings
  • Format validation — regex patterns for emails, phone numbers, etc.

2. Output Encoding — Don’t Execute Untrusted Content

Always encode data before rendering it in a different context:

# output_encoding.py — Output encoding in different contexts

class OutputEncoder:
    """Demonstrates secure output encoding for different contexts."""

    @staticmethod
    def html_encode(text: str) -> str:
        """Encode text for HTML context (prevents XSS)."""
        replacements = {
            '&': '&amp;',
            '<': '&lt;',
            '>': '&gt;',
            '"': '&quot;',
            "'": '&#x27;',
            '/': '&#x2F;',
        }
        for char, encoded in replacements.items():
            text = text.replace(char, encoded)
        return text

    @staticmethod
    def html_attribute_encode(text: str) -> str:
        """Encode text for HTML attribute context."""
        # More aggressive encoding needed in attributes
        text = text.replace('"', '&quot;')
        text = text.replace("'", '&#x27;')
        text = text.replace('`', '&#x60;')
        text = text.replace(' ', '&#x20;')
        return text

    @staticmethod
    def javascript_encode(text: str) -> str:
        """Encode text for JavaScript string context."""
        replacements = {
            '\\': '\\\\',
            "'": "\\'",
            '"': '\\"',
            '\n': '\\n',
            '\r': '\\r',
            '\t': '\\t',
            '<': '\\x3C',
            '>': '\\x3E',
        }
        for char, encoded in replacements.items():
            text = text.replace(char, encoded)
        return text

    @staticmethod
    def url_encode(text: str) -> str:
        """Encode text for URL context."""
        import urllib.parse
        return urllib.parse.quote(text, safe='')

# Example
encoder = OutputEncoder()
user_input = "<script>alert('xss')</script>"
print(f"HTML encoded: {encoder.html_encode(user_input)}")
# &lt;script&gt;alert(&#x27;xss&#x27;)&lt;&#x2F;script&gt;

Context matters:

  • HTML body → &lt; not <
  • HTML attribute → Encode quotes and spaces
  • JavaScript string → Escape ', ", \, \n
  • URL → Percent-encode special characters
  • SQL → Use parameterized queries (never escape manually)

3. Authentication — Verify Identity Securely

# authentication_secure.py — Authentication best practices
import hashlib
import secrets
import os

class SecureAuth:
    """Demonstrates secure authentication practices."""

    @staticmethod
    def hash_password(password: str) -> tuple[str, str]:
        """Hash a password using bcrypt (not MD5 or SHA-1!)."""
        # In production use bcrypt, Argon2, or scrypt
        # This example uses PBKDF2 which is acceptable
        salt = os.urandom(32)
        key = hashlib.pbkdf2_hmac(
            'sha256',
            password.encode('utf-8'),
            salt,
            100000,  # Iterations — should be updated as hardware improves
            dklen=32
        )
        return salt.hex(), key.hex()

    @staticmethod
    def verify_password(password: str, salt_hex: str, key_hex: str) -> bool:
        """Verify a password against stored hash."""
        salt = bytes.fromhex(salt_hex)
        stored_key = bytes.fromhex(key_hex)
        key = hashlib.pbkdf2_hmac(
            'sha256',
            password.encode('utf-8'),
            salt,
            100000,
            dklen=32
        )
        return secrets.compare_digest(key, stored_key)

    @staticmethod
    def generate_session_token() -> str:
        """Generate a cryptographically secure session token."""
        return secrets.token_hex(32)  # 256 bits of entropy

    @staticmethod
    def verify_mfa_code(secret: str, code: str) -> bool:
        """Verify a TOTP-based MFA code."""
        # In production use pyotp library
        # TOTP: Time-based One-Time Password
        import hmac
        import time
        interval = int(time.time()) // 30  # 30-second window
        key = secret.encode()
        msg = interval.to_bytes(8, 'big')
        expected = hmac.new(key, msg, 'sha1').digest()[:4]
        # Convert to 6-digit code
        expected_code = str(int.from_bytes(expected, 'big') % 10**6).zfill(6)
        return hmac.compare_digest(code, expected_code)

# Example
salt, key = SecureAuth.hash_password("MyS3cur3P@ss!")
print(f"Salt: {salt[:16]}...")
print(f"Key:  {key[:16]}...")
print(f"Verify: {SecureAuth.verify_password('MyS3cur3P@ss!', salt, key)}")  # True
print(f"Wrong:  {SecureAuth.verify_password('wrong', salt, key)}")          # False

Authentication checklist:

  • Password hashing: Use bcrypt, Argon2, or scrypt — never MD5, SHA-1, or unsalted hashes
  • MFA: Always offer multi-factor authentication
  • Account lockout: Lock after 5-10 failed attempts (with gradual delays)
  • Rate limiting: Limit login attempts per IP and per user
  • Session tokens: Cryptographically random, 256+ bits
  • Secure storage: Hash passwords, never store plaintext

4. Session Management — Protect Active Sessions

# session_management.py — Secure session management

class SecureSession:
    """Demonstrates secure session management practices."""

    def __init__(self):
        self.sessions = {}  # In production, use Redis or similar

    def create_session(self, user_id: str, mfa_verified: bool = False) -> str:
        """Create a new session with security context."""
        import secrets
        import time

        session_id = secrets.token_hex(32)
        self.sessions[session_id] = {
            "user_id": user_id,
            "created_at": time.time(),
            "last_activity": time.time(),
            "ip_address": None,     # Set on first request
            "user_agent": None,     # Set on first request
            "mfa_verified": mfa_verified,
            "impersonating": False
        }
        return session_id

    def validate_session(self, session_id: str,
                         ip_address: str,
                         user_agent: str,
                         timeout_minutes: int = 30) -> bool:
        """Validate a session with security checks."""
        import time

        session = self.sessions.get(session_id)
        if not session:
            return False

        # Check timeout
        elapsed = time.time() - session["last_activity"]
        if elapsed > timeout_minutes * 60:
            del self.sessions[session_id]
            return False

        # Check IP address change (optional — can be problematic for mobile)
        # if session["ip_address"] and session["ip_address"] != ip_address:
        #     return False  # Possible session hijacking

        # Update last activity
        session["last_activity"] = time.time()
        if not session["ip_address"]:
            session["ip_address"] = ip_address
        if not session["user_agent"]:
            session["user_agent"] = user_agent

        return True

    def invalidate_session(self, session_id: str):
        """Log out a session."""
        self.sessions.pop(session_id, None)

    def rotate_session(self, session_id: str) -> str:
        """Session rotation after privilege escalation."""
        session = self.sessions.get(session_id)
        if not session:
            return None
        self.invalidate_session(session_id)
        return self.create_session(
            session["user_id"],
            session.get("mfa_verified", False)
        )

Session security rules:

  • Regenerate session ID after login and privilege changes
  • Set HttpOnly and Secure flags on cookies
  • Use SameSite=Strict or Lax
  • Set session timeout (15-30 minutes of inactivity)
  • Never expose session IDs in URLs

5. Error Handling — Don’t Leak Information

# error_handling.py — Secure error handling

class SecureErrorHandler:
    """Demonstrates secure error handling — never expose internals."""

    @staticmethod
    def safe_error_response(error: Exception, debug_mode: bool = False) -> dict:
        """Return a safe error response without leaking internals."""

        # Map to user-safe message
        error_map = {
            ValueError: "Invalid input provided.",
            PermissionError: "You don't have permission to perform this action.",
            FileNotFoundError: "The requested resource was not found.",
            ConnectionError: "A temporary error occurred. Please try again.",
        }

        # Get safe message
        error_type = type(error)
        user_message = error_map.get(error_type, "An unexpected error occurred.")

        # Build response
        response = {
            "error": True,
            "message": user_message,
            "status_code": 500
        }

        # Only in debug mode — log details internally
        if debug_mode:
            print(f"DEBUG — {error_type.__name__}: {str(error)}")
            # In production, use proper logging:
            # logging.exception(f"Error processing request: {error}")

        return response

    @staticmethod
    def log_error(error: Exception, context: dict = None):
        """Securely log errors — never log sensitive data."""
        import logging

        # Sanitize context — remove sensitive fields
        sensitive_keys = {"password", "secret", "token", "authorization",
                         "credit_card", "ssn", "api_key"}
        safe_context = {}
        if context:
            for k, v in context.items():
                if k.lower() in sensitive_keys:
                    safe_context[k] = "[REDACTED]"
                else:
                    safe_context[k] = v

        logging.error(f"Error: {type(error).__name__}: {error}", extra=safe_context)

# Example
try:
    raise ValueError("Database connection failed: server=prod-db-01:3306")
except ValueError as e:
    response = SecureErrorHandler.safe_error_response(e, debug_mode=True)
    print(f"User sees: {response['message']}")
    # User sees: "Invalid input provided."
    # Not: "Database connection failed: server=prod-db-01:3306"

6. File Upload Security

# file_upload.py — Secure file upload handling

class SecureFileUpload:
    """Demonstrates secure file upload practices."""

    ALLOWED_TYPES = {
        'image/jpeg': '.jpg',
        'image/png': '.png',
        'image/gif': '.gif',
        'application/pdf': '.pdf',
        'text/plain': '.txt',
    }
    MAX_FILE_SIZE = 5 * 1024 * 1024  # 5MB

    @classmethod
    def validate_upload(cls, file_data: bytes,
                        content_type: str,
                        filename: str) -> tuple[bool, str]:
        """Validate a file upload for security."""
        # Check file size
        if len(file_data) > cls.MAX_FILE_SIZE:
            return False, "File exceeds maximum size (5MB)"

        # Check content type
        if content_type not in cls.ALLOWED_TYPES:
            return False, f"File type '{content_type}' is not allowed"

        # Verify extension matches content type
        expected_ext = cls.ALLOWED_TYPES[content_type]
        if not filename.lower().endswith(expected_ext):
            return False, "File extension does not match content type"

        # Verify magic bytes (not just trusting the MIME type)
        if content_type == 'image/jpeg':
            if not file_data.startswith(b'\xff\xd8\xff'):
                return False, "File is not a valid JPEG"
        elif content_type == 'image/png':
            if not file_data.startswith(b'\x89PNG\r\n\x1a\n'):
                return False, "File is not a valid PNG"

        return True, "File is valid"

    @classmethod
    def secure_filename(cls, filename: str) -> str:
        """Generate a safe filename for storage."""
        import uuid
        import re
        # Keep only safe characters
        safe = re.sub(r'[^\w\-\.]', '_', filename)
        # Prepend UUID to prevent path traversal and overwrites
        ext = safe.split('.')[-1] if '.' in safe else ''
        return f"{uuid.uuid4().hex}.{ext}"

Common Secure Coding Mistakes

1. Trusting User Input

The most common vulnerability. Validate, sanitize, and encode everything from users.

2. Rolling Your Own Cryptography

Never write your own encryption or hash functions. Use well-vetted libraries. Homemade crypto is almost always broken.

3. Logging Sensitive Information

Passwords, credit cards, and session tokens should never appear in logs. Implement log sanitization.

4. Not Handling All Error Paths

A try/except that catches all exceptions and does nothing silently hides security-relevant errors. Always log and handle appropriately.

5. Using Insecure Randomness

random module is for non-security purposes. Use secrets (Python) or crypto.randomBytes (Node.js) for security.

6. Hardcoding Secrets

API keys, passwords, and tokens in source code will eventually leak. Use environment variables or a secrets manager.

7. Ignoring Third-Party Dependencies

Your code may be secure, but a vulnerable npm package or pip library can compromise everything. Run SCA tools regularly.

Practice Questions

1. What is the difference between input validation and output encoding?

Input validation rejects bad data before processing. Output encoding transforms data for safe rendering in different contexts (HTML, JavaScript, SQL). Both are needed — they solve different problems.

2. Why should you never use MD5 or SHA-1 for password hashing?

They’re designed for speed, making them easy to brute force. Use bcrypt, Argon2, or scrypt which are intentionally slow and include salts.

3. What is session fixation and how do you prevent it?

An attacker sets a user’s session ID before they log in. Prevent by regenerating the session ID after login (session rotation).

4. Why should error messages not reveal internal details?

Attackers use error messages to learn about your infrastructure: server names, database types, file paths, and code structure. Always return generic user-safe messages.

5. Challenge: Write a secure function that validates and sanitizes a URL provided by user input.

Parse with urllib.parse, whitelist http/https schemes, validate format, encode special characters, and verify the domain isn’t an IP address (if policy requires domains only).

Mini Project: Secure Code Reviewer

# secure_code_reviewer.py
# Automated security review for common code issues

import ast
import re

class SecureCodeReviewer:
    """Scan Python source code for security issues."""

    CHECKS = {
        "hardcoded_password": {
            "pattern": r'(password|passwd|secret|api_key)\s*=\s*["\'][^"\']+["\']',
            "severity": "HIGH",
            "message": "Possible hardcoded credential"
        },
        "eval_usage": {
            "pattern": r'\beval\s*\(',
            "severity": "CRITICAL",
            "message": "Use of eval() — code injection risk"
        },
        "exec_usage": {
            "pattern": r'\bexec\s*\(',
            "severity": "CRITICAL",
            "message": "Use of exec() — code injection risk"
        },
        "insecure_hash": {
            "pattern": r'hashlib\.(md5|sha1)\b',
            "severity": "MEDIUM",
            "message": "Use of weak hashing algorithm"
        },
        "sql_injection": {
            "pattern": r'execute\(.*["\'].*\{.*["\']\)',
            "severity": "CRITICAL",
            "message": "Possible SQL injection (string interpolation in query)"
        },
        "pickle_usage": {
            "pattern": r'import pickle|pickle\.(load|loads)',
            "severity": "HIGH",
            "message": "Pickle deserialization can execute arbitrary code"
        },
        "assert_usage": {
            "pattern": r'\bassert\b',
            "severity": "LOW",
            "message": "Assertions disabled with -O flag — use proper validation"
        },
    }

    def review_file(self, filepath: str) -> list[dict]:
        """Review a Python file for security issues."""
        findings = []
        try:
            with open(filepath, 'r', encoding='utf-8') as f:
                content = f.read()
                lines = content.split('\n')
        except (FileNotFoundError, IOError) as e:
            return [{"error": f"Cannot read file: {e}"}]

        for check_name, config in self.CHECKS.items():
            for i, line in enumerate(lines, 1):
                if re.search(config["pattern"], line):
                    findings.append({
                        "file": filepath,
                        "line": i,
                        "severity": config["severity"],
                        "check": check_name,
                        "message": config["message"],
                        "code": line.strip()
                    })

        return findings

# Example review
reviewer = SecureCodeReviewer()
sample_code = """
import hashlib
password = "SuperSecret123!"
eval(user_input)
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
"""
with open("/tmp/vulnerable_code.py", "w") as f:
    f.write(sample_code)

results = reviewer.review_file("/tmp/vulnerable_code.py")
for r in results:
    print(f"[{r['severity']}] Line {r['line']}: {r['message']}")
    print(f"  Code: {r['code']}")

FAQ

Which programming language is most secure?
No language is inherently secure — it’s how you use it. Memory-safe languages (Rust, Go, Python, Java) prevent common C/C++ vulnerabilities like buffer overflows. However, SQL injection and XSS are possible in any language.
What is the OWASP ASVS?
The Application Security Verification Standard — a framework of security requirements organized by verification level (L1-L3). Use it as a checklist during design and code review.
How do I start learning secure coding?
Learn OWASP Top 10 vulnerabilities first. Then practice on deliberately vulnerable apps (OWASP Juice Shop, DVWA). Write code to exploit, then fix each vulnerability.
Do I need to be a security expert to write secure code?
No — but you need security awareness. Use frameworks that handle security (parameterized queries, template auto-escaping), run SAST tools, and review your dependencies. Most vulnerabilities are prevented by following basic principles.
What’s the single most important secure coding practice?
Input validation — never trust anything from outside your application. Combined with output encoding (never trust when rendering), this prevents most web vulnerabilities.

Try It Yourself

  1. Take a piece of code you’ve written recently (or use the vulnerable sample above)
  2. Run it through a SAST tool like Bandit: pip install bandit && bandit -r .
  3. Fix each finding
  4. Verify with bandit again

This is the same workflow DodaTech engineers follow for Doda Browser and Durga Antivirus Pro development — write, scan, fix, verify.

What’s Next

What’s Next

Congratulations on completing this Secure Coding tutorial! Here’s where to go from here:

  • Practice daily — Consistency is more important than long study sessions
  • Build a project — Apply what you learned by building something real
  • Explore related topics — Check out other tutorials in the same category
  • Join the community — Discuss with other learners and share your progress

Remember: every expert was once a beginner. Keep coding!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro