Passwords¶
How CRUDAuth stores passwords, lets an OAuth-only user set one, and resets a forgotten one.
Storage and POST /set-password come with the base app; the reset flow additionally needs
email configured:
auth = CRUDAuth(
session=get_session, user_model=User, SECRET_KEY="change-me",
email=EmailConfig(sender=MySender(), frontend_url="https://app.example.com"), # for reset
)
app.include_router(auth.router) # adds /set-password and /password/reset-*
See Getting started for the base app and Email flows for the sender.
Storage¶
Passwords are hashed with bcrypt, after a SHA-256 pre-hash so bcrypt's 72-byte ceiling never
silently truncates a long password. Verification returns False for a malformed stored hash
instead of raising, so a corrupted row is a clean "invalid password", not a 500. You never
handle the plaintext beyond the route that receives it.
MIN_PASSWORD_LENGTH (8) is enforced on registration and on password reset. A custom
register_schema governs its own field constraints.
Setting a password on an OAuth-only account¶
A user who signed up through OAuth has no usable password (the stored value is an unusable
sentinel). POST /set-password is a built-in route that lets them set their first one while
authenticated. The active session is the re-authentication, since there's no current password
to check.
# 1. set the password (the OAuth session cookie + CSRF header authenticate the call)
curl -X POST http://localhost:8000/set-password \
-H "X-CSRF-Token: <token>" -H "Content-Type: application/json" \
-b "session_id=<cookie>" \
-d '{"new_password": "a-strong-one"}'
# 2. the account can now log in by password too
curl -X POST http://localhost:8000/login \
-d "username=alice&password=a-strong-one"
This is set, not change: it refuses with 400 if the account already has a usable
password (use the reset flow to change an existing one), and it doesn't evict other sessions,
because establishing a first credential isn't a compromise response.
Changing a known password¶
A signed-in user with a password changes it through POST /change-password, a built-in route. The
current password is the re-authentication (the active session/token proves presence, the current
password proves intent), so no token round-trip is needed.
curl -X POST http://localhost:8000/change-password \
-H "X-CSRF-Token: <token>" -H "Content-Type: application/json" \
-b "session_id=<cookie>" \
-d '{"current_password": "old-one", "new_password": "a-new-strong-one"}'
Allowed over any transport: CSRF is automatic on the session path, and bearer has no CSRF surface.
A wrong current password is a 401; an account with no usable password is a 400 (use
/set-password instead). Because a password change is a compromise response, it bumps
token_version (evicting bearer tokens) and revokes the user's other sessions, keeping the
current one, and fires the on_after_password_changed hook.
Resetting a forgotten password¶
A user who can't log in uses the email reset flow: POST /password/reset-request sends a
link, and POST /password/reset-confirm sets the new password. See Email flows
for the setup. The reset also bumps token_version, so any bearer tokens issued before the
reset stop working.