immich-app/immich · error · UnauthorizedException
Incorrect email or password
Error message
Incorrect email or password
What it means
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.
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.
Example fix
// before
const { accessToken } = await api.login({ email: ' Admin@example.com ', password });
// after
const email = 'admin@example.com'.trim();
if (!(await api.validatePassword(email, password))) {
throw new Error('Check credentials or reset password via admin');
} Defensive patterns
Strategy: try-catch
Validate before calling
const email = emailInput.trim().toLowerCase();
if (!email || !password) throw new Error('Email and password are required'); Try / catch
try { return await api.login({ email, password }); } catch (e) { if (e.status === 401) { showCredentialError(); logLocalAttempt(); } else throw e; } Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- Password login has been disabled
- Not in maintenance mode
- Wrong password
- Wrong PIN code
- Asset dimensions are not available for editing
AI-assisted analysis of immich-app/immich@f48d4b3321 (2026-09-15).
Data as JSON: /api/errors/97072d93fc214430.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/auth.service.ts:73
};
};
@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)