immich-app/immich · error · BadRequestException
This OAuth account has already been linked to another user.
Error message
This OAuth account has already been linked to another user.
What it means
When linking an OAuth identity to the current user, the service looks up whether the OAuth `sub` is already attached to a different user account. If so, linking would create a duplicate identity binding, so it refuses with a BadRequestException.
Solutions
- Unlink the OAuth account from the other user first, then retry the link
- Sign in with the OAuth account and link to the desired local account from that session
- Use a different OAuth account that is not already bound to another user
Example fix
// before POST /oauth/link // google account already bound to user B // after DELETE /oauth/unlink (as user B) // then POST /oauth/link as user A
Defensive patterns
Strategy: try-catch
Validate before calling
const dup = await userRepository.getByOAuthId(sub); if (dup && dup.id !== currentUserId) alert('already linked elsewhere'); Try / catch
catch (e) { if (e.status === 400 && /already been linked/.test(e.message)) { /* show unlink-first guidance */ } } Prevention
- Check oauth bindings before attempting link
- Provide an unlink UI
- Avoid shared OAuth accounts
When it happens
Trigger: Calling the link endpoint with an OAuth account whose `sub` is already stored on another user (duplicate.id !== auth.user.id).
Common situations: User tries to link a Google/Microsoft/OIDC account already used to sign into another local account; shared service accounts; testing with a second account reusing the same OAuth identity.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- OAuth authentication failed
- OAuth link account failed: sub is already linked to another…
- Error backchannel logout: token validation failed
- Error in OAuth discovery
- Failed to fetch picture
AI-assisted analysis of immich-app/immich@f48d4b3321 (2026-09-15).
Data as JSON: /api/errors/69c5e224cba5e5fe.
Report an issue: GitHub.
Appendix: source
Thrown at server/src/services/auth.service.ts:425
if (!expectedState?.length) {
throw new BadRequestException('OAuth state is missing');
}
const codeVerifier = dto.codeVerifier ?? this.getCookieCodeVerifier(headers);
if (!codeVerifier?.length) {
throw new BadRequestException('OAuth code verifier is missing');
}
const { oauth } = await this.getConfig({ withCache: false });
const {
profile: { sub: oauthId },
sid,
idToken,
} = await this.oauthRepository.getProfileAndOAuthSid(oauth, dto.url, expectedState, codeVerifier);
const duplicate = await this.userRepository.getByOAuthId(oauthId);
if (duplicate && duplicate.id !== auth.user.id) {
this.logger.warn(`OAuth link account failed: sub is already linked to another user (${duplicate.email}).`);
throw new BadRequestException('This OAuth account has already been linked to another user.');
}
if (auth.session && (sid || idToken)) {
await this.sessionRepository.update(auth.session.id, {
oauthSid: sid,
oauthBearerToken: idToken,
});
}
const user = await this.userRepository.update(auth.user.id, { oauthId });
return mapUserAdmin(user);
}
async unlink(auth: AuthDto): Promise<UserAdminResponseDto> {
if (auth.session) {
await this.sessionRepository.update(auth.session.id, { oauthSid: null, oauthBearerToken: null });
}
View on GitHub (pinned to f48d4b3321)