immich-app/immich · warning

Failed login attempt for user

Error message

Failed login attempt for user ${dto.email} from ip address ${details.clientIp}

What it means

On login, the service looks up the user by email and compares the bcrypt password (against a dummy hash when the user does not exist, to keep timing constant). If any check fails, this warning is logged with the attempted email and client IP and an UnauthorizedException('Incorrect email or password') is thrown to the client.

Solutions

  1. Confirm the email exists and the password is correct; reset via 'Forgot password' if needed.
  2. If the account is OAuth-only, log in via OAuth or set a password through admin settings.
  3. Repeated attempts from one IP: check rate-limiting / consider blocking the address.
  4. If the password is definitely correct, re-hash/reset the password in admin user settings.
Defensive patterns

Strategy: validation

Validate before calling

if (!email.includes('@') || password.length === 0) {
  throw new BadRequestException('Email and password are required');
}

Try / catch

try {
  await api.login({ email, password });
} catch (e) {
  if (e instanceof UnauthorizedException) {
    showToast('Incorrect email or password');
  } else throw e;
}

Prevention

When it happens

Trigger: POST /auth/login with credentials where the email is unknown, the user has no password set (OAuth-only account), or the bcrypt comparison fails.

Common situations: Typo in email or password; user created via OAuth never set a server password; Caps Lock/keyboard layout issues; brute-force probing (watch the IP in logs); after password change with a cached client.

Related errors


AI-assisted analysis of immich-app/immich@f48d4b3321 (2026-09-15). Data as JSON: /api/errors/6ff21c157a888350. Report an issue: GitHub.

Appendix: source

Thrown at server/src/services/auth.service.ts:72

    uri: string;
  };
};

@Injectable()
export class AuthService extends BaseService {
  async login(dto: LoginCredentialDto, details: LoginDetails) {
    const config = await this.getConfig({ withCache: false });
    if (!config.passwordLogin.enabled) {
      throw new UnauthorizedException('Password login has been disabled');
    }

    const user = await this.userRepository.getByEmail(dto.email, { withPassword: true });
    // Always run bcrypt so response time is constant regardless of whether the email
    // is registered, preventing timing-based user enumeration.
    const isAuthenticated = this.cryptoRepository.compareBcrypt(dto.password, user?.password ?? LOGIN_DUMMY_HASH);

    if (!user || !user.password || !isAuthenticated) {
      this.logger.warn(`Failed login attempt for user ${dto.email} from ip address ${details.clientIp}`);
      throw new UnauthorizedException('Incorrect email or password');
    }

    return this.createLoginResponse(user, details);
  }

  async logout(auth: AuthDto, authType: AuthType): Promise<LogoutResponseDto> {
    let oauthBearerToken: string | undefined;
    if (auth.session) {
      const session = await this.sessionRepository.get(auth.session.id);
      oauthBearerToken = session?.oauthBearerToken ?? undefined;
      await this.sessionRepository.delete(auth.session.id);
      await this.eventRepository.emit('SessionDelete', { sessionId: auth.session.id });
    }

    return {
      successful: true,
      redirectUri: await this.getLogoutEndpoint(authType, oauthBearerToken),

View on GitHub (pinned to f48d4b3321)