SonarSource/sonarqube · error
User '{}' matched by external login, but the stored external
Error message
User '{}' matched by external login, but the stored external ID differs - possible recycled external username What it means
UserRegistrarImpl.validateExternalIdToAvoidLoginRecycling throws when a user matched by external login (provider username) has a stored external ID that differs from the ID the identity provider now reports. userExternalIdMatchesLogin compares user.getExternalId() with user.getExternalLogin(); when they diverge, the provider login was likely recycled or reassigned, and authentication is aborted to prevent account takeover. The warning log names the provider login before failAuthenticationException is thrown.
Source
Thrown at server/sonar-webserver-auth/src/main/java/org/sonar/server/authentication/UserRegistrarImpl.java:160
private static void validateEmailToAvoidLoginRecycling(UserIdentity userIdentity, UserDto user, Source source) {
String dbEmail = user.getEmail();
if (dbEmail == null) {
return;
}
String externalEmail = userIdentity.getEmail();
if (!dbEmail.equalsIgnoreCase(externalEmail)) {
LOGGER.warn("User with login '{}' tried to login with email '{}' which doesn't match the email on record '{}'", userIdentity.getProviderLogin(), externalEmail, dbEmail);
throw failAuthenticationException(userIdentity, source);
}
}
private static void validateExternalIdToAvoidLoginRecycling(UserIdentity userIdentity, UserDto user, Source source) {
if (!userExternalIdMatchesLogin(user)) {
LOGGER.warn("User '{}' matched by external login, but the stored external ID differs - possible recycled external username", userIdentity.getProviderLogin());
throw failAuthenticationException(userIdentity, source);
}
}
private static boolean userExternalIdMatchesLogin(UserDto user) {
return Objects.equals(user.getExternalId(), user.getExternalLogin());
}
private static AuthenticationException failAuthenticationException(UserIdentity userIdentity, Source source) {
String message = String.format("Failed to authenticate with login '%s'", userIdentity.getProviderLogin());
return authException(userIdentity, source, message, message);
}
private static AuthenticationException authException(UserIdentity userIdentity, Source source, String message, String publicMessage) {
return AuthenticationException.newBuilder()
.setSource(source)
.setLogin(userIdentity.getProviderLogin())
.setMessage(message)View on GitHub (pinned to 184c821202)
Solutions
- Verify at the identity provider whether the account was deleted/recreated; if so, update or delete the stale SonarQube user so a fresh provisioning stores the new external ID.
- Correct the user's externalId/externalLogin data (update web service or SQL via support guidance) so externalId matches the provider's current identifier.
- If migration caused the divergence, re-run the identity sync/provisioning to repopulate external IDs from the provider.
- If this is an actual account takeover attempt, keep the block and investigate the provider account activity.
Example fix
// before: sonar user 'jdoe' externalId='jdoe' (legacy), GitHub now reports externalId='1234567' // after: re-provision or correct so externalId == externalLogin-based match passes Users > jdoe > delete stale account; user logs in again via GitHub; new record created with externalId=1234567
Defensive patterns
Strategy: validation
Validate before calling
// Before reusing a provider login, verify stored identifiers
boolean safe = Objects.equals(user.getExternalId(), user.getExternalLogin());
if (!safe) { reProvisionUser(user.getLogin()); } Prevention
- Never delete-and-recreate provider accounts with the same username; expect the external ID to change.
- After IdP migrations, run a re-provisioning/sync so SonarQube external IDs are refreshed.
- Monitor this warning — it may indicate an attempted account takeover; investigate rather than blindly 'fixing' data.
When it happens
Trigger: During ALM authentication, validateAlmSpecificData -> validateExternalIdToAvoidLoginRecycling finds an existing UserDto whose externalLogin matches the incoming provider login but whose externalId (stable provider user id) does not equal that login string.
Common situations: Identity provider deleted and recreated a user with the same username but a different numeric ID; SonarQube was migrated from a setup where externalId was stored differently (e.g. login instead of numeric GitHub id); provisioning tooling changed how externalId is populated.
Related errors
- User with login '{}' tried to login with email '{}' which do
- Invalid personal access token
- Unable to contact Bitbucket Cloud servers: Configure the OAu
- Unable to contact Bitbucket Cloud servers: Check your creden
- Invalid personal access token
AI-assisted analysis of SonarSource/sonarqube@184c821202 (2026-09-09).
Data as JSON: /api/errors/e617ea03d515a455.
Report an issue: GitHub.