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
- Confirm the email exists and the password is correct; reset via 'Forgot password' if needed.
- If the account is OAuth-only, log in via OAuth or set a password through admin settings.
- Repeated attempts from one IP: check rate-limiting / consider blocking the address.
- 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
- Set a server password for OAuth-only accounts if password login is expected.
- Use a password manager to avoid typos; reset password when unsure.
- Watch logs for repeated failures from one IP and apply rate limiting/firewalling.
- Keep client sessions refreshed after password changes.
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
- OAuth login failed
- Attempted to clear cache, but rmtree is not safe on this…
- authToken is required
- Denied access to admin only route
- Denied access to non-shared route
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)