nestrs-oauth2 ships a small, focused password-hashing layer behind
the password feature. Pick a backend (Bcrypt for legacy hash
migration, Argon2id for new deployments), call hash /
verify from your service layer, or let the #[derive(HashOnNew)]
macro construct a row whose password field is hashed for you.
Enable the feature
password umbrella pulls in both backends plus the
nestrs-oauth2-macros proc-macro crate that powers HashOnNew.
Enable just one backend if you know you’ll never need the other:
Hash and verify
verify (and its alias verify_any) returns Result<bool, HashError>:
Ok(true)— password matches.Ok(false)— password does not match (hash was valid).Err(UnknownPrefix(_))— the stored hash isn’t$2...(bcrypt) or$argon2...(argon2). Indicates a corrupt DB row or a hash from an unsupported algorithm.Err(BcryptDisabled)/Err(Argon2Disabled)— the matched backend’s feature wasn’t enabled.
Pick a backend explicitly
hash_with and verify_with take an explicit Backend:
Backend::default() is Argon2id — Argon2 is the modern OWASP
recommendation; Bcrypt is retained only for hash migration. Mixing
hashes from different algorithms in the same column is fine as long
as you use verify / verify_any (which dispatches on the prefix)
rather than verify_with with a hard-coded backend.
The PasswordHasher trait
For code that wants to swap algorithms at runtime — e.g. a migration
script that rehashes every row from Bcrypt to Argon2id — there’s a
trait with a concrete impl per backend:
BcryptHasher uses the bcrypt crate’s DEFAULT_COST (12 as of
bcrypt 0.15). Argon2Hasher uses Argon2::default() (Argon2id,
m=19456 KiB, t=2, p=1). Both are Copy + Default + Debug so
they slot into your DI registry as Arc<dyn PasswordHasher> if
you want backend choice at request time.
#[derive(HashOnNew)] — the new_with_hashed constructor
The proc-macro derive generates an inherent
new_with_hashed(...) constructor that hashes every field tagged
#[hash] via nestrs_oauth2::password::hash before storing it:
- Takes every named field as a constructor parameter, in struct
declaration order. Marked fields take
String(the plain input); the macro hashes them before storage. - Uses the absolute path
::nestrs_oauth2::password::hash(...)for the hash call — nousestatement is needed in user code. - Preserves generics (
impl<T: Clone> HashOnNew for Foo<T> { ... }works as you’d expect). - Errors at compile time if you mark a field with
#[hash(args)]— backend selection happens at hash time, not derive time.
Migration story
Most production migrations move from Bcrypt to Argon2id without service downtime. The recommended pattern:- New rows: hash with
hash(Argon2id default) → store the new hash alongside any existing columns. - Login: call
verify(plain, &stored)— it dispatches on the hash prefix and works against both Bcrypt and Argon2id rows. - On successful login of a legacy Bcrypt row: rehash with
hash_with(plain, Backend::Argon2)and update the column. - After the rollout window, drop the Bcrypt
password-bcryptfeature and confirm no$2...rows remain in production.
verify returns Err(HashError::UnknownPrefix(_)) for hashes that
aren’t Bcrypt or Argon2 — surface that as a 500 to operations so
you can clean up bad rows, never as a “wrong password”.
Why these choices
- Argon2id as default — modern OWASP recommendation, resistant to GPU and side-channel attacks.
- No
bcryptre-export at the crate root —bcryptandargon2are implementation details of thepasswordmodule. If you need raw access (custom parameters, custom salt format), depend on those crates directly. - Prefix auto-detection in
verify_any— lets you mix legacy and new hashes in the same column during migration without branching at every call site. Result<bool, _>instead ofbool— distinguishes “hash format unknown” (real error) from “password mismatch” (normal auth failure). Use.unwrap_or(false)at the call site if you want the bool shortcut.
See also
mintlify-docs/guides/oauth2— the parent OAuth2 guide (client, resource server, social, guard).nestrs_oauth2::Backend— the backend enum.nestrs_oauth2::PasswordHasher— the trait for swappable backends.nestrs_oauth2::HashError— the error type returned by hash/verify.