{"record":{"id":"5b1a5dcedf8200bd","repo":"langgenius/dify","slug":"authentication-failed","errorCode":"authentication_failed","errorMessage":"Invalid email or password.","messagePattern":"Invalid email or password\\.","errorType":"error_code","errorClass":"AuthenticationFailedError","httpStatus":401,"severity":"error","filePath":"api/controllers/console/auth/login.py","lineNumber":165,"sourceCode":"                data = invitation_data.get(\"data\", {})\n                invitee_email = data.get(\"email\") if data else None\n                invitee_email_normalized = invitee_email.lower() if isinstance(invitee_email, str) else invitee_email\n                if invitee_email_normalized != normalized_email:\n                    _log_console_login_failure(\n                        email=normalized_email,\n                        reason=LoginFailureReason.INVALID_INVITATION_EMAIL,\n                    )\n                    raise InvalidEmailError()\n            account = _authenticate_account_with_case_fallback(\n                request_email, normalized_email, req_data.password, invite_token\n            )\n        except services.errors.account.AccountLoginError:\n            _log_console_login_failure(email=normalized_email, reason=LoginFailureReason.ACCOUNT_BANNED)\n            raise AccountBannedError()\n        except services.errors.account.AccountPasswordError as exc:\n            AccountService.add_login_error_rate_limit(normalized_email)\n            _log_console_login_failure(email=normalized_email, reason=LoginFailureReason.INVALID_CREDENTIALS)\n            raise AuthenticationFailedError() from exc\n        tenants = TenantService.get_join_tenants(account, session=db.session())\n        if len(tenants) == 0:\n            if (\n                FeatureService.is_workspace_creation_allowed()\n                and not FeatureService.get_license().workspaces.is_available()\n            ):\n                raise WorkspacesLimitExceeded()\n            else:\n                return SimpleResultOptionalDataResponse(\n                    result=\"fail\",\n                    data=\"workspace not found, please contact system admin to invite you to join in a workspace\",\n                ).model_dump(mode=\"json\")\n\n        token_pair = AccountService.login(account=account, session=db.session(), ip_address=extract_remote_ip(request))\n        AccountService.reset_login_error_rate_limit(normalized_email)\n\n        # Create response with cookies instead of returning tokens in body\n        # response-contract:ignore cookie-bearing Flask response","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/langgenius/dify/blob/ef8544b173fd6cd7a8e71df2cab576e52bebbfbc/api/controllers/console/auth/login.py#L147-L183","documentation":"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.","triggerScenarios":"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>.","commonSituations":"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.","solutions":["Use the forgot-password flow to reset the password.","Confirm the email is correct and the account exists in this environment.","Verify the client is encrypting the password the same way the server's @decrypt_password_field expects (check the public key / RSA setup).","After 5 failures the account hits error 422 lockout; stop retrying and reset the password."],"exampleFix":"// before\nfetch('/console/api/login', {body: JSON.stringify({email, password: plaintext})})\n// after - ensure password is encrypted with the server's public key first\nconst encrypted = await encryptWithPublicKey(serverPublicKey, plaintext)\nfetch('/console/api/login', {body: JSON.stringify({email, password: encrypted})})","handlingStrategy":"try-catch","validationCode":"# Sanity-check the encrypted password payload length before sending\nencrypted = encrypt_for_server(plaintext_password)\nif len(encrypted) < EXPECTED_MIN_LEN:\n    abort('Password encryption failed; refresh the page.')\nsubmit_login(email, encrypted)","typeGuard":"null","tryCatchPattern":"from controllers.console.auth.error import AuthenticationFailedError\nattempts = 0\nwhile attempts < 3:\n    try:\n        return do_login()\n    except AuthenticationFailedError:\n        attempts += 1\noffer_password_reset()","preventionTips":["Confirm the client uses the server's current RSA public key for the password field.","Rate-limit retries client-side and offer password reset early.","Verify the user is on the correct environment URL."],"tags":["auth","login","credentials","authentication","password"],"backgroundTag":null,"analyzedSha":"ef8544b173fd6cd7a8e71df2cab576e52bebbfbc","analyzedAt":"2026-08-12T05:15:17.394Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}