immich-app/immich · error · UnauthorizedException
Incorrect email or password
Error message
Incorrect email or password
What it means
Thrown by AuthService.login when authentication fails: the user is not found, has no password set, or the bcrypt comparison fails. The check runs a dummy bcrypt hash against a constant (LOGIN_DUMMY_HASH) when the user is missing so response time stays constant and prevents user enumeration. The email is logged at warn level with the client IP, and a 401 Unauthorized is returned.
Source
Thrown at server/src/services/auth.service.ts:72
};
};
@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 199723261c)
Solutions
- Verify the email is registered and the password is correct.
- If the account is OAuth-only, use the OAuth login flow instead.
- Use the password reset flow if the password is forgotten.
- Check server logs for the warn line to confirm whether the email itself is unknown.
Defensive patterns
Strategy: try-catch
Validate before calling
// Light client-side validation; full validation happens server-side.
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(email) || !password) {
throw new Error('Enter a valid email and password.');
}
await api.post('/auth/login', { email, password }); Try / catch
try {
await api.post('/auth/login', { email, password });
} catch (e) {
if (e.response?.status === 401) {
showCredentialError(); // do NOT reveal whether email vs password was wrong
} else throw e;
} Prevention
- Validate email format client-side, but never reveal email-vs-password specifics.
- Offer OAuth or password reset when login repeatedly fails.
- Confirm OAuth-only accounts are not attempting password login.
When it happens
Trigger: POST /auth/login with an unregistered email; correct email but wrong password; user exists but has no password (OAuth-only account) yet tries password login.
Common situations: Typos in email or password; user created via OAuth trying password login; migrated users without passwords; caps-lock / wrong keyboard layout.
Related errors
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/97072d93fc214430.
Report an issue: GitHub.