Password Hashing
Never store plain-text passwords.
If the database is compromised (a SQL injection, a leaked backup, a misconfigured cloud bucket), every user's credentials are exposed. Worse, users routinely reuse passwords across sites, so a leak from one site can turn into account takeovers everywhere else that password is reused.
The fix is to store a hash, not the password.
What Hashing Does
A cryptographic hash function takes an input of any size and returns a fixed-size string.
A good password hash function has two properties:
When a user logs in, the server hashes the password they submit and compares it to the stored hash. If they match, the password was correct. The server never has to know what the password actually was.
pwdlib and Argon2id
uv add "pwdlib[argon2]"
The [argon2] extra installs the Argon2 backend that PasswordHash.recommended() selects.
The security.py Module
Password handling lives in a new file, src/release_tracker/security.py. This file will grow over the next two pages to hold token generation and the get_current_user dependency too. For now, two functions:
from pwdlib import PasswordHash
password_hash = PasswordHash.recommended()
def get_password_hash(password: str) -> str:
return password_hash.hash(password)
def verify_password(plain_password: str, hashed_password: str) -> bool:
return password_hash.verify(plain_password, hashed_password)
PasswordHash.recommended() returns a configured hasher with sensible defaults. get_password_hash produces the hash to store in the database; verify_password checks a candidate password against a stored hash and returns a boolean.
Hashing solves password storage. The next problem is keeping the user identified across requests, which is what JWTs are for.