Auth, Secrets, and Hardening
Password hashing done right, session hygiene, secret management, and the security headers that cost nothing.
Passwords: hashing is not encryption
You must never be able to read a password back. Store hash(password + salt)
with a slow, memory-hard function โ bcrypt, scrypt, or Argon2. Slow is the
point: an offline attacker with your database makes billions of guesses;
each guess must cost real time. (hashlib.sha256 is fast โ that's why it's
wrong here.) Salts kill rainbow tables; work factors make brute force
uneconomical; rate limiting on the login endpoint caps online guessing.
Sessions and cookies
After login, the server issues a random session ID (โฅ128 bits of entropy) kept in a cookie. Hardening flags:
HttpOnlyโ JavaScript cannot read it (XSS can't steal it),Secureโ it never travels over plain HTTP,SameSite=Laxโ cross-site requests don't carry it (kills most CSRF),- expiring and rotating IDs on privilege changes.
CSRF, the leftover case: an attacker's page makes the victim's browser send an authenticated request. SameSite blocks most; a synchronizer token (a random value in the form that must match the session) blocks the rest.
Secrets: config, not code
A secret in source control is compromised forever (history survives deletion).
The discipline: secrets live in environment variables or a secrets manager,
loaded at startup, validated with the same parse-don't-validate discipline as
any boundary input (SECRET_KEY missing โ refuse to boot, don't default).
Different environments get different secrets; logs and error reports are
scrubbed of them; dependencies are pinned and audited (pip audit) because
your supply chain is also an attack surface.
Security headers: the cheapest fixes in the file
| Header | Effect |
|---|---|
| Content-Security-Policy | blocks script from unapproved origins |
| X-Content-Type-Options: nosniff | browser won't reinterpret responses |
| Strict-Transport-Security | forces HTTPS for future visits |
| X-Frame-Options: DENY | blocks clickjacking via framing |
Verification closes the loop
A fix without a test is a hope. Every security change ships with a test that proves the attack fails now: the traversal path is rejected, the injection string is stored as a literal, the wrong owner gets 403/404. That's the checkpoint below.