Module 06 - Security Engineering
Reading time: ~12 minutes | Level: Advanced - Engineering
Before reading further, look at this code and decide whether it is secure:
import hashlib
import os
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy import text
app = FastAPI()
def hash_password(password: str) -> str:
return hashlib.sha256(password.encode()).hexdigest()
def verify_password(password: str, stored_hash: str) -> bool:
return hash_password(password) == stored_hash
@app.post("/login")
async def login(username: str, password: str, db=Depends(get_db)):
query = f"SELECT * FROM users WHERE username = '{username}'"
result = await db.execute(text(query))
user = result.fetchone()
if user and verify_password(password, user.password_hash):
token = os.urandom(32).hex()
return {"token": token}
raise HTTPException(status_code=401, detail="Invalid credentials")
@app.get("/admin")
async def admin_panel(token: str):
# TODO: verify token
return {"admin": True}
Count the vulnerabilities. There are at least seven - and every one of them ships to production in real codebases every day.
The password hashing uses SHA-256, which is fast and unsalted - an attacker with a stolen database cracks every password in minutes with rainbow tables. The comparison uses ==, which is vulnerable to timing attacks. The SQL query is built with string formatting - classic SQL injection. The login endpoint accepts username and password as query parameters, leaking credentials into server logs and browser history. The token is a random hex string with no structure, expiration, or signature - it cannot be verified without a database round trip. The /admin endpoint has a TODO comment instead of actual authorization. And the whole application has no rate limiting, so an attacker can brute-force credentials at thousands of requests per second.
This is not a contrived example. This is the pattern that developers produce when they know Python but have never studied security engineering.
This module changes that.
Why Security Engineering Is Non-Negotiable
Security is not a feature you add after the application works. It is a property of the system that either exists in the architecture or does not. Bolting security onto an insecure design is like adding a lock to a door with no walls.
Every Python system that handles user data, authentication, or network requests is an attack surface. The question is not whether attackers will probe it - the question is whether your engineering will withstand the probe.
Understanding security at engineering depth means:
- Choosing the right cryptographic primitives for the right problem - not SHA-256 for passwords, not bcrypt for data integrity
- Implementing authentication flows that are correct by construction - JWTs with proper signing, expiration, and audience validation
- Writing code that is structurally immune to injection - not by remembering to sanitize, but by using APIs that make injection impossible
- Managing secrets so they never appear in source code, logs, environment dumps, or error traces
- Auditing dependencies, configurations, and deployment patterns for known vulnerability classes
:::danger Security Is Not Optional Knowledge Every vulnerability in the opening example has caused real data breaches at real companies. Equifax (143 million records) fell to an unpatched dependency. Heartbleed was a buffer over-read in OpenSSL. The 2012 LinkedIn breach exposed 117 million passwords hashed with unsalted SHA-1. These are not edge cases - they are the predictable consequences of engineers who treated security as someone else's problem. :::
What You Will Learn
This module covers seven lessons plus two projects:
Lesson 01 - Cryptographic Hashing
The difference between data hashing and password hashing - and why using the wrong one is a catastrophic mistake. hashlib for data integrity: SHA-256, SHA-3, BLAKE2. Password hashing with bcrypt and argon2: work factors, salting, why slowness is a feature. Why MD5 and SHA-1 are broken for passwords but still useful for checksums. Timing attacks on string comparison and the hmac.compare_digest defense. Pepper: the secret salt that never touches the database.
Lesson 02 - JWT Authentication
JSON Web Token structure: header, payload, signature. Signing algorithms: HMAC-SHA256 (symmetric) vs RSA-SHA256 (asymmetric) and when to use each. Token lifecycle: creation, validation, expiration, refresh. Implementing JWT auth with PyJWT. Common JWT mistakes that create vulnerabilities: alg: none, symmetric key confusion, missing audience validation, tokens that never expire. Refresh token rotation and token revocation strategies.
Lesson 03 - OAuth 2.0 and OIDC
Why OAuth exists - the delegation problem that passwords cannot solve. The authorization code flow step by step. PKCE: why public clients need proof keys. Scopes and consent. OpenID Connect: identity on top of authorization. Implementing OAuth with authlib. Keycloak integration: configuring realms, clients, and role mappings. The difference between access tokens, ID tokens, and refresh tokens.
Lesson 04 - Input Validation and Sanitization
Why validation is the first line of defense. Pydantic validators as security boundaries: constrained types, custom validators, strict mode. SQL injection via string formatting - the attack and the structural fix. Cross-site scripting (XSS): how it works, where Python backends are vulnerable, output encoding. Path traversal: ../../etc/passwd and how pathlib prevents it. Server-side request forgery (SSRF): when your server becomes the attacker's proxy. The principle: validate at the boundary, trust nothing from the wire.
Lesson 05 - SQL Injection Prevention
The anatomy of a SQL injection attack. Why parameterized queries make injection structurally impossible. SQLAlchemy text() with bound parameters vs f-string formatting. ORM safety guarantees - and the edge cases where ORMs do not protect you. Raw SQL audit patterns: how to find every unparameterized query in a codebase. Building a pre-commit hook that flags dangerous SQL patterns.
Lesson 06 - Secrets Management
Why environment variables are better than hardcoded strings - and why they are still not good enough. python-dotenv for local development. AWS Secrets Manager and HashiCorp Vault for production. The secret lifecycle: creation, rotation, revocation, and audit. git-secrets and pre-commit hooks to prevent accidental commits. What to do when a secret is leaked: the incident response playbook. Twelve-Factor App secret principles.
Lesson 07 - Secure Coding Patterns
Defense in depth: why one layer is never enough. Least privilege: why your database connection should not be root. CORS configuration: what it protects and what it does not. Rate limiting with slowapi and token bucket algorithms. Content Security Policy headers. Dependency auditing with pip-audit and safety. Static analysis with bandit: finding vulnerabilities before they ship. Security-focused code review checklist.
The Security Engineering Mental Model
Security engineering is not a checklist - it is a way of thinking about systems. The mental model has three layers:
Layer 1 - Primitives are the cryptographic building blocks. You do not invent these; you select the right one and use it correctly. The wrong primitive (SHA-256 for passwords) or the wrong usage (ECB mode for encryption) creates vulnerabilities that no amount of application logic can fix.
Layer 2 - Application is where most security work happens. Authentication verifies identity. Authorization enforces permissions. Input validation rejects malformed data before it reaches business logic. Injection prevention ensures that data never becomes code.
Layer 3 - Operations covers everything outside the application code. Secrets must be rotated. Dependencies must be audited. Logs must capture security events without leaking sensitive data. This layer is where most breaches actually occur - not because the code is wrong, but because the operations around it are.
:::note Security Is a System Property No single lesson in this module makes your application secure. Security emerges from the correct combination of all layers. A perfectly implemented JWT system is useless if the signing key is committed to Git. Flawless input validation is irrelevant if a dependency has a known RCE vulnerability. Each lesson is a necessary condition - none is sufficient alone. :::
Projects
Project A - Authenticated API with RBAC
Build a FastAPI application with production-grade security: password hashing with argon2, JWT authentication with RS256 signing, role-based access control (admin, editor, viewer), Pydantic input validation on every endpoint, parameterized database queries, security headers (CORS, CSP, X-Content-Type-Options), rate limiting, and structured security logging. The project integrates every lesson into a single deployable system.
Project B - Security Audit Tool
Build a CLI tool that scans Python projects for common security vulnerabilities: hardcoded secrets in source files, unparameterized SQL queries, use of eval() or exec(), insecure hash functions for password storage, missing input validation patterns, and outdated dependencies with known CVEs. The tool produces a structured report with severity ratings and remediation guidance.
Prerequisites
- Python Intermediate - especially Module 06 (APIs and Web Basics with FastAPI), Module 07 (Databases with SQLAlchemy), and Module 04 (Testing and Quality)
- Pydantic v2 models and validators - you will use these as security boundaries
- Basic understanding of HTTP: methods, headers, status codes, request/response lifecycle
- Command-line comfort: you will work with
openssl,curl, and environment variables
You do not need prior security experience. The module builds from cryptographic primitives upward. But you should be comfortable building a FastAPI application with database access - the security patterns in this module wrap around systems you already know how to build.
The Engineering Standard
Every concept in this module is grounded in how production systems are actually attacked and defended:
- OWASP Top 10 is the industry-standard classification of web application vulnerabilities - this module covers injection, broken authentication, sensitive data exposure, security misconfiguration, and insufficient logging
- NIST password guidelines (SP 800-63B) recommend memory-hard hash functions like argon2 - not bcrypt, not PBKDF2, and certainly not SHA-256
- RFC 7519 (JWT) and RFC 6749 (OAuth 2.0) are the protocol specifications - this module teaches the specs, not just the libraries
- CWE/CVE databases catalog real vulnerabilities in real systems - the examples in this module reference actual CVEs to show what happens when these patterns are violated
:::tip Think Like an Attacker The most effective way to go through this module is adversarial. For every pattern you learn, ask: how would I break this? For every defense, ask: what does it not protect against? Security engineers do not just build walls - they think about how walls fail. The opening code example has seven vulnerabilities because the developer thought about functionality, not failure modes. Train yourself to see both. :::
How to Follow Along
Every lesson includes runnable code. Set up your environment once:
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# Cryptographic hashing
pip install bcrypt argon2-cffi
# JWT and OAuth
pip install PyJWT cryptography authlib httpx
# Web framework and validation
pip install fastapi uvicorn pydantic sqlalchemy aiosqlite
# Security tooling
pip install python-dotenv pip-audit bandit slowapi
# Testing
pip install pytest pytest-asyncio httpx
For the OAuth and Keycloak lessons, you will also need Docker:
docker run -d --name keycloak \
-p 8080:8080 \
-e KC_BOOTSTRAP_ADMIN_USERNAME=admin \
-e KC_BOOTSTRAP_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:24.0.0 start-dev
Key Takeaways
- Security is a system property that must be designed in from the beginning - it cannot be bolted on after the fact
- This module covers the full security stack: cryptographic primitives, authentication and authorization, input validation, injection prevention, secrets management, and secure coding patterns
- Every concept maps to real-world attack classes documented in OWASP, CWE, and CVE databases
- Two projects integrate the material end-to-end: a secure API with RBAC and a security audit CLI tool
- Prerequisites are Intermediate Python (FastAPI, SQLAlchemy, Pydantic, testing) - no prior security experience required
- The engineering standard connects directly to NIST guidelines, RFC specifications, and production security practices
What's Next
Lesson 01 starts with the most fundamental security primitive - the hash function. You will see why hashlib.sha256(password) is a catastrophic mistake, why bcrypt's slowness is its greatest feature, and why argon2 is the NIST-recommended standard for password hashing in 2025. The lesson builds from the mathematics of hash functions to production-ready password storage that withstands offline brute-force attacks with GPU clusters.
