immich-app/immich · error · UnauthorizedException

Password login has been disabled

Error message

Password login has been disabled

What it means

AuthService.login first reads the server config and rejects password-based sign-in with this UnauthorizedException (401) when the passwordLogin.enabled setting is false. This is a deliberate server policy, not a credential problem — typically set when the instance is OAuth/OIDC-only.

Solutions

  1. Log in via the configured OAuth/OIDC flow instead of POST /api/auth/login.
  2. Re-enable password login in Administration > Settings > Authentication (or set server config passwordLogin.enabled=true) if password access is intended.
  3. For programmatic access, create an API key and use the x-api-key header instead of password auth.
  4. Check the server config (GET /api/server/config) to confirm passwordLogin.enabled before attempting password auth.

Example fix

// before
await immichApi.login({ email, password }); // 401: password login disabled
// after
const cfg = await immichApi.getServerConfig();
if (!cfg.passwordLoginEnabled) {
  client.setApiKey(process.env.IMMICH_API_KEY); // use API key instead
}
Defensive patterns

Strategy: fallback

Validate before calling

const cfg = await api.getServerConfig();
if (!cfg.passwordLoginEnabled) console.warn('Password login disabled; use OAuth or API key');

Try / catch

try { await api.login({ email, password }); } catch (e) { if (e.status === 401) startOAuthFlow(); else throw e; }

Prevention

When it happens

Trigger: POST /api/auth/login on an Immich server whose config has passwordLogin.enabled === false (admin disabled password login in Server Settings, or config via env/immich.json omits/enables only OAuth).

Common situations: Self-hosters switching to SSO (OAuth) and disabling password login, then old clients/CLIs/scripts still authenticating with email+password; fresh setups where password login was disabled by template config; users who never set a password because accounts were provisioned via OAuth.

Related errors


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

Appendix: source

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

export type ValidateRequest = {
  headers: IncomingHttpHeaders;
  queryParams: Record<string, string>;
  metadata: {
    sharedLinkRoute: boolean;
    adminRoute: boolean;
    /** `false` explicitly means no permission is required, which otherwise defaults to `all` */
    permission?: Permission | false;
    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) {

View on GitHub (pinned to f48d4b3321)