unslothai/unsloth · error · HTTPException

Password change required

Error message

Password change required

What it means

HTTP 403 raised when the authenticated user's record has must_change_password set, the request is not on an allowed password-change route, and the token is not a desktop token. It enforces a password-reset gate: until the user sets a new password, all other API access is refused.

Source

Thrown at studio/backend/auth/authentication.py:299

    record = get_user_and_secret(subject)
    if record is None:
        raise HTTPException(
            status_code = status.HTTP_401_UNAUTHORIZED,
            detail = "Invalid or expired token",
        )

    _salt, _pwd_hash, jwt_secret, must_change_password = record
    try:
        payload = jwt.decode(token, jwt_secret, algorithms = [ALGORITHM])
        if payload.get("sub") != subject:
            raise HTTPException(
                status_code = status.HTTP_401_UNAUTHORIZED,
                detail = "Invalid token payload",
            )
        is_desktop = payload.get("desktop") is True
        if must_change_password and not allow_password_change and not is_desktop:
            raise HTTPException(
                status_code = status.HTTP_403_FORBIDDEN,
                detail = "Password change required",
            )
        return subject, credential_generation(jwt_secret)
    except jwt.InvalidTokenError:
        raise HTTPException(
            status_code = status.HTTP_401_UNAUTHORIZED,
            detail = "Invalid or expired token",
        )

View on GitHub (pinned to 203007d190)

Solutions

  1. Complete the password change via the change-password endpoint/UI; the flag clears and normal access resumes.
  2. Custom clients should detect 403 with detail 'Password change required' and route the user to the change-password flow.
  3. Admins: confirm the flag is intended (it is usually set by an explicit reset action).

Example fix

# before
resp = requests.get(f"{base}/api/things", headers=auth)

# after
resp = requests.get(f"{base}/api/things", headers=auth)
if resp.status_code == 403 and 'Password change required' in resp.text:
    requests.post(f"{base}/auth/change-password", json={"old": old, "new": new}, headers=auth)
Defensive patterns

Strategy: try-catch

Try / catch

try:
    client.get('/api/data')
except HTTPStatusError as e:
    if e.response.status_code == 403 and 'Password change required' in e.response.text:
        prompt_password_change()  # complete the flow, then retry

Prevention

When it happens

Trigger: An admin reset the user's password (setting the must-change flag) and the user keeps calling normal endpoints with the old still-valid JWT; first-login password policies; the user skips the forced change-password screen in a custom client.

Common situations: Password-expiry policies; security incidents forcing credential rotation; custom API clients that never implemented the change-password step and only call regular endpoints.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/763e5cdd88ede18f. Report an issue: GitHub.