immich-app/immich · error · BadRequestException
OAuth authentication failed
Error message
OAuth authentication failed
What it means
BadRequestException (HTTP 400) thrown inside the email-linking branch of AuthService.callback. When no user matches the OAuth sub but a user with the same normalized email exists AND already has a non-empty oauthId, Immich refuses to overwrite the link. The generic message avoids revealing which account owns the email; the debug log records the conflict.
Source
Thrown at server/src/services/auth.service.ts:318
const url = this.resolveRedirectUri(oauth, dto.url);
const {
profile,
sid: oauthSid,
idToken: oauthBearerToken,
} = await this.oauthRepository.getProfileAndOAuthSid(oauth, url, expectedState, codeVerifier);
const normalizedEmail = profile.email ? profile.email.trim().toLowerCase() : undefined;
const { autoRegister, defaultStorageQuota, storageLabelClaim, storageQuotaClaim, roleClaim } = oauth;
this.logger.debug(`Logging in with OAuth: ${JSON.stringify(profile)}`);
let user: UserAdmin | undefined = await this.userRepository.getByOAuthId(profile.sub);
// link by email
if (!user && normalizedEmail) {
const emailUser = await this.userRepository.getByEmail(normalizedEmail);
if (emailUser) {
if (emailUser.oauthId) {
this.logger.debug('OAuth login conflict: email already linked to different account');
throw new BadRequestException('OAuth authentication failed');
}
user = await this.userRepository.update(emailUser.id, { oauthId: profile.sub });
}
}
const role = this.getRoleClaim(profile, roleClaim);
const isAdmin = role === 'admin';
if (user && role && isAdmin !== user.isAdmin) {
user = await this.userRepository.update(user.id, { isAdmin });
}
// register new user
if (!user) {
if (!autoRegister) {
this.logger.warn(
`Unable to register ${profile.sub}/${normalizedEmail || '(no email)'}. User does not exist and auto registering is disabled. To enable set OAuth Auto Register to true in admin settings.`,
);View on GitHub (pinned to 199723261c)
Solutions
- Log in with the original OAuth identity that is already linked to that email.
- Have an admin clear the existing oauthId on the user row so the new identity can link.
- Disable auto-link-by-email in OAuth settings if you do not want silent linking.
- Check the server debug log 'OAuth login conflict: email already linked' to confirm the diagnosis.
Example fix
// before
POST /oauth/callback { url } // from a new IdP account sharing the existing email
// -> 400 OAuth authentication failed
// after
// admin runs (or user logs in with the original IdP):
UPDATE users SET "oauthId" = '' WHERE email = 'user@example.com';
// user retries OAuth login Defensive patterns
Strategy: try-catch
Validate before calling
// No safe client-side pre-check (the conflict is server-side). // Mitigate by ensuring each user has at most one linked IdP identity.
Type guard
function isOauthLinkConflict(message: string): boolean {
return message === 'OAuth authentication failed';
} Try / catch
try {
await axios.post('/oauth/callback', { url });
} catch (e) {
if (e.response?.data?.message === 'OAuth authentication failed') {
showHelp('Sign in with the originally linked OAuth account, or ask an admin to clear the link.');
} else throw e;
} Prevention
- Document one-identity-per-user policy for users.
- Admin tooling to list/clear oauthId per user.
- Avoid mixing IdPs that share email spaces.
When it happens
Trigger: POST /oauth/callback for an IdP identity whose email matches an Immich user that is already linked to a different IdP account (different sub). The user must log in with the original linked identity instead.
Common situations: User changed IdP accounts (e.g. new Google account with same Gmail); IdP rotated subject IDs; admin pre-registered users by email and the user tries OAuth before being linked; multiple IdPs sharing email space.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- This OAuth account has already been linked to another user.
- OAuth is not enabled
- OAuth state is missing
- OAuth code verifier is missing
- OAuth profile does not have an email address
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/d8f013e80cd1576c.
Report an issue: GitHub.