Bcrypt is the conservative default: battle-tested, well-audited, available in every language. Argon2id is newer, more parameterizable (memory cost, parallelism), and theoretically stronger against GPU attacks. If your team is comfortable tuning Argon2id’s t/m/p parameters and your stack supports it, prefer Argon2id. Otherwise bcrypt at cost=12 is fine.
passlib[bcrypt] with rounds=12 takes ~250 ms per hash — slow enough to deter brute force.
Argon2id: t=3 iterations, m=64 MiB memory, p=1 parallelism; tweak for your threat model.
Migration: hash new passwords with the new algo; re-hash on next login for old ones.
cleartext -> bcrypt/argon2id -> store hash
(next login verifies; old hash migrates on success)
Pick what you can tune correctly. bcrypt cost=12 is the safe default; Argon2id if you’ll watch the params.