{"record":{"id":"97072d93fc214430","repo":"immich-app/immich","slug":"incorrect-email-or-password","errorCode":null,"errorMessage":"Incorrect email or password","messagePattern":"Incorrect email or password","errorType":"exception","errorClass":"UnauthorizedException","httpStatus":401,"severity":"error","filePath":"server/src/services/auth.service.ts","lineNumber":73,"sourceCode":"  };\n};\n\n@Injectable()\nexport class AuthService extends BaseService {\n  async login(dto: LoginCredentialDto, details: LoginDetails) {\n    const config = await this.getConfig({ withCache: false });\n    if (!config.passwordLogin.enabled) {\n      throw new UnauthorizedException('Password login has been disabled');\n    }\n\n    const user = await this.userRepository.getByEmail(dto.email, { withPassword: true });\n    // Always run bcrypt so response time is constant regardless of whether the email\n    // is registered, preventing timing-based user enumeration.\n    const isAuthenticated = this.cryptoRepository.compareBcrypt(dto.password, user?.password ?? LOGIN_DUMMY_HASH);\n\n    if (!user || !user.password || !isAuthenticated) {\n      this.logger.warn(`Failed login attempt for user ${dto.email} from ip address ${details.clientIp}`);\n      throw new UnauthorizedException('Incorrect email or password');\n    }\n\n    return this.createLoginResponse(user, details);\n  }\n\n  async logout(auth: AuthDto, authType: AuthType): Promise<LogoutResponseDto> {\n    let oauthBearerToken: string | undefined;\n    if (auth.session) {\n      const session = await this.sessionRepository.get(auth.session.id);\n      oauthBearerToken = session?.oauthBearerToken ?? undefined;\n      await this.sessionRepository.delete(auth.session.id);\n      await this.eventRepository.emit('SessionDelete', { sessionId: auth.session.id });\n    }\n\n    return {\n      successful: true,\n      redirectUri: await this.getLogoutEndpoint(authType, oauthBearerToken),\n    };","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/immich-app/immich/blob/f48d4b332127ad365ba256108799ca8f571d2dd5/server/src/services/auth.service.ts#L55-L91","documentation":"After fetching the user by email (with password hash) and comparing bcrypt (against a dummy hash when the user is unknown, for constant-time behavior), login throws this UnauthorizedException (401) when the user does not exist, has no password set, or the password does not match. Failed attempts are logged server-side with the client IP.","triggerScenarios":"POST /api/auth/login where getByEmail returns null (unknown email), user.password is null (OAuth-provisioned user), or compareBcrypt(dto.password, hash) is false (wrong password).","commonSituations":"Typos in email/password; users created via OAuth who never set a local password (or used the 'Change Password' reset flow incorrectly); caps-lock/whitespace in credentials; clients pointing at the wrong Immich instance; after admin reset of a user's password.","solutions":["Verify the email exists and the password is correct; use the web UI to confirm the credentials work.","For OAuth-provisioned users, use the OAuth login flow or set a password via the admin/user password reset flow.","Trim whitespace and check for case issues in the email before retrying.","If credentials are definitely correct, check server logs for 'Failed login attempt' to confirm which email/ip was attempted, and confirm you are hitting the intended instance.","Repeated failures: reset the password through an admin or the offline password-reset CLI."],"exampleFix":"// before\nconst { accessToken } = await api.login({ email: ' Admin@example.com ', password });\n// after\nconst email = 'admin@example.com'.trim();\nif (!(await api.validatePassword(email, password))) {\n  throw new Error('Check credentials or reset password via admin');\n}","handlingStrategy":"try-catch","validationCode":"const email = emailInput.trim().toLowerCase();\nif (!email || !password) throw new Error('Email and password are required');","typeGuard":null,"tryCatchPattern":"try { return await api.login({ email, password }); } catch (e) { if (e.status === 401) { showCredentialError(); logLocalAttempt(); } else throw e; }","preventionTips":["Trim and normalize email input","Use the web UI to verify credentials before scripting them","Set a local password for OAuth-provisioned users who need API/login access","Point clients at the correct instance/URL"],"tags":["auth","authentication","credentials","bcrypt","immich"],"backgroundTag":"authentication-required","analyzedSha":"f48d4b332127ad365ba256108799ca8f571d2dd5","analyzedAt":"2026-09-15T07:20:19.675Z","contentChangedAt":"2026-09-15T07:20:19.675Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}