Skip to content

Password reset

Password reset is opt-in. You enable it by providing an async function that sends the reset email.

Setup

async def send_reset_email(email: str, token: str) -> None:
    """Send a password reset email. Called by bluefox-auth."""
    reset_url = f"https://app.example.com/reset-password?token={token}"
    await your_email_service.send(
        to=email,
        subject="Reset your password",
        body=f"Click here to reset your password: {reset_url}",
    )

BluefoxAuth(app, settings, password_reset_send_fn=send_reset_email)

The function receives the user's email and a signed JWT token (valid for 1 hour). Without this hook, the /password-reset endpoint returns 501 Not Implemented.

Flow

1. User requests a reset

POST /auth/password-reset
Content-Type: application/json

{"email": "user@example.com"}

Response (always the same, regardless of whether the email exists):

{"message": "If that email exists, a reset link has been sent."}

This prevents email enumeration. If the email is unknown or the user is inactive, no email is sent.

2. User confirms the reset

The user clicks the link in the email, your frontend collects the new password, and sends:

POST /auth/password-reset/confirm
Content-Type: application/json

{"token": "eyJhbG...", "new_password": "my-new-strong-password"}

Response:

{"message": "Password has been reset."}

Security

  • One-time use — once the password is changed, the token cannot be reused. This is enforced by checking updated_at > created_at (the user record was modified after initial creation) and updated_at >= iat (the modification happened after the token was issued).
  • 1 hour expiry — tokens expire after 60 minutes
  • Type-checked — a password reset token cannot be used for email verification, and vice versa
  • Silent on unknown email — prevents user enumeration

Frontend example

// Request reset
await fetch("/auth/password-reset", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ email: userEmail }),
});

// Confirm reset (after user clicks email link)
const token = new URLSearchParams(window.location.search).get("token");
await fetch("/auth/password-reset/confirm", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ token, new_password: newPassword }),
});