Security¶
An overview of the security measures built into bluefox-auth.
Password handling¶
- bcrypt — passwords are hashed with bcrypt directly (no wrapper libraries)
- 72-byte limit — bcrypt silently truncates at 72 bytes; bluefox-auth validates this at the schema level and rejects passwords exceeding the limit
- Minimum 8 characters — enforced on registration and password reset
- Timing-safe login — when a user is not found, a dummy bcrypt hash is still verified to prevent timing-based enumeration
JWT tokens¶
All tokens include:
jti— unique token ID (UUID hex)iat— issued-at timestampexp— expiration timestampaud— audience claim (default:"bluefox")type— token type (access,refresh,password_reset,email_verify)sub— user ID
Token types are enforced on decode — a refresh token cannot be used as an access token, and vice versa.
Refresh token rotation¶
Each login creates a token family. When a refresh token is used:
- The old token is marked as revoked
- A new token is issued in the same family
- The new token is stored server-side in the
refresh_tokenstable
Reuse detection: if a revoked token is replayed, the entire family is revoked — invalidating all tokens from that login session. This protects against token theft scenarios where an attacker and legitimate user race to use the same token.
Cookie security¶
When using cookie transport, tokens are set with:
| Cookie | HttpOnly | Secure | SameSite | Path |
|---|---|---|---|---|
| Access token | Yes | Yes* | Lax | / |
| Refresh token | Yes | Yes* | Lax | Auth prefix |
| CSRF token | No | Yes* | Lax | / |
*Secure defaults to True. Set cookie_secure=False for local development.
The refresh token cookie is scoped to the auth prefix path, limiting its exposure to only the auth endpoints.
CSRF protection¶
bluefox-auth uses the plain double-submit cookie pattern:
- On login/refresh, a CSRF token is set as a non-HttpOnly cookie (so JavaScript can read it)
- For mutating requests (POST/PUT/PATCH/DELETE) using cookie transport, the client must send the CSRF token value in the
X-CSRF-Tokenheader - The server compares the cookie value and header value using
secrets.compare_digest
Bearer requests bypass CSRF — the Authorization: Bearer <token> header is proof of JavaScript access to the token, which a CSRF attacker wouldn't have.
Safe methods (GET, HEAD, OPTIONS) skip CSRF validation — they shouldn't have side effects.
Secret key validation¶
AuthSettings validates the secret key on startup:
- Must be at least 32 characters
- Cannot be empty or a known placeholder (
"change-me-in-production","secret","changeme") - Excluded from
repr()andmodel_dump()to prevent accidental logging
Password reset¶
- Silent on unknown email — always returns the same response regardless of whether the email exists, preventing enumeration
- Silent on inactive users — no email is sent for inactive accounts
- One-time use — enforced by checking
updated_at > created_at(the record was modified after initial creation) andupdated_at >= iat(the modification happened after the token was issued); once the password is changed, the token cannot be replayed - 1 hour expiry — tokens expire after 60 minutes
- Type-checked — password reset tokens cannot be used for email verification, and vice versa
Email verification¶
- One-time use — once
email_verifiedis set toTrue, the same token cannot be used again - 24 hour expiry — tokens expire after 24 hours
- Requires authentication — the request endpoint requires a valid access token
- Short-circuits — if the user is already verified, returns immediately without sending another email