langgenius/dify · error · AuthenticationFailedError

authentication_failed

authentication_failed

Error message

Invalid email or password.

What it means

Raised in POST /console/api/login (HTTP 401, code authentication_failed) when _authenticate_account_with_case_fallback raises services.errors.account.AccountPasswordError for both the original and lowercased email. The controller increments the login error rate-limit counter and re-raises as AuthenticationFailedError. Deliberately returns 401 (not 404) to avoid leaking which emails exist.

Source

Thrown at api/controllers/console/auth/login.py:165

                data = invitation_data.get("data", {})
                invitee_email = data.get("email") if data else None
                invitee_email_normalized = invitee_email.lower() if isinstance(invitee_email, str) else invitee_email
                if invitee_email_normalized != normalized_email:
                    _log_console_login_failure(
                        email=normalized_email,
                        reason=LoginFailureReason.INVALID_INVITATION_EMAIL,
                    )
                    raise InvalidEmailError()
            account = _authenticate_account_with_case_fallback(
                request_email, normalized_email, req_data.password, invite_token
            )
        except services.errors.account.AccountLoginError:
            _log_console_login_failure(email=normalized_email, reason=LoginFailureReason.ACCOUNT_BANNED)
            raise AccountBannedError()
        except services.errors.account.AccountPasswordError as exc:
            AccountService.add_login_error_rate_limit(normalized_email)
            _log_console_login_failure(email=normalized_email, reason=LoginFailureReason.INVALID_CREDENTIALS)
            raise AuthenticationFailedError() from exc
        tenants = TenantService.get_join_tenants(account, session=db.session())
        if len(tenants) == 0:
            if (
                FeatureService.is_workspace_creation_allowed()
                and not FeatureService.get_license().workspaces.is_available()
            ):
                raise WorkspacesLimitExceeded()
            else:
                return SimpleResultOptionalDataResponse(
                    result="fail",
                    data="workspace not found, please contact system admin to invite you to join in a workspace",
                ).model_dump(mode="json")

        token_pair = AccountService.login(account=account, session=db.session(), ip_address=extract_remote_ip(request))
        AccountService.reset_login_error_rate_limit(normalized_email)

        # Create response with cookies instead of returning tokens in body
        # response-contract:ignore cookie-bearing Flask response

View on GitHub (pinned to ef8544b173)

Solutions

  1. Use the forgot-password flow to reset the password.
  2. Confirm the email is correct and the account exists in this environment.
  3. Verify the client is encrypting the password the same way the server's @decrypt_password_field expects (check the public key / RSA setup).
  4. After 5 failures the account hits error 422 lockout; stop retrying and reset the password.

Example fix

// before
fetch('/console/api/login', {body: JSON.stringify({email, password: plaintext})})
// after - ensure password is encrypted with the server's public key first
const encrypted = await encryptWithPublicKey(serverPublicKey, plaintext)
fetch('/console/api/login', {body: JSON.stringify({email, password: encrypted})})
Defensive patterns

Strategy: try-catch

Validate before calling

# Sanity-check the encrypted password payload length before sending
encrypted = encrypt_for_server(plaintext_password)
if len(encrypted) < EXPECTED_MIN_LEN:
    abort('Password encryption failed; refresh the page.')
submit_login(email, encrypted)

Type guard

null

Try / catch

from controllers.console.auth.error import AuthenticationFailedError
attempts = 0
while attempts < 3:
    try:
        return do_login()
    except AuthenticationFailedError:
        attempts += 1
offer_password_reset()

Prevention

When it happens

Trigger: POST /console/api/login with a wrong password (or a non-existent email) after both case variants of the email have been tried by _authenticate_account_with_case_fallback. Each failure increments login_error_rate_limit:<email>.

Common situations: User mistyped the password; password was recently changed; user is on the wrong environment (e.g., staging creds in prod); account email is wrong or non-existent; client did not apply the same encryption the @decrypt_password_field decorator expects.

Related errors


AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12). Data as JSON: /api/errors/5b1a5dcedf8200bd. Report an issue: GitHub.