{"record":{"id":"6ade7e8d69e769b0","repo":"OrchardCMS/OrchardCore","slug":"provider-login-loginprovider-is-already-linked-for-user","errorCode":null,"errorMessage":"Provider {login.LoginProvider} is already linked for {user.UserName}","messagePattern":"Provider (.+?) is already linked for (.+?)","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/OrchardCore/OrchardCore.Users.Core/Services/UserStore.cs","lineNumber":487,"sourceCode":"        var users = await _session.Query<User, UserByRoleNameIndex>(u => u.RoleName == normalizedRoleName).ListAsync(cancellationToken);\n        return users == null ? [] : users.ToList<IUser>();\n    }\n\n    #endregion IUserRoleStore<IUser>\n\n    #region IUserLoginStore<IUser>\n\n    public Task AddLoginAsync(IUser user, UserLoginInfo login, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(user);\n\n        ArgumentNullException.ThrowIfNull(login);\n\n        if (user is User u)\n        {\n            if (u.LoginInfos.Any(i => i.LoginProvider == login.LoginProvider))\n            {\n                throw new InvalidOperationException($\"Provider {login.LoginProvider} is already linked for {user.UserName}\");\n            }\n\n            u.LoginInfos.Add(login);\n        }\n\n        return Task.CompletedTask;\n    }\n\n    public async Task<IUser> FindByLoginAsync(string loginProvider, string providerKey, CancellationToken cancellationToken)\n    {\n        return await _session.Query<User, UserByLoginInfoIndex>(u => u.LoginProvider == loginProvider && u.ProviderKey == providerKey).FirstOrDefaultAsync(cancellationToken);\n    }\n\n    public Task<IList<UserLoginInfo>> GetLoginsAsync(IUser user, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(user);\n\n        if (user is User u)","sourceCodeStart":469,"sourceCodeEnd":505,"githubUrl":"https://github.com/OrchardCMS/OrchardCore/blob/4306c0717fe573f6fca1b4955909ddab6a192807/src/OrchardCore/OrchardCore.Users.Core/Services/UserStore.cs#L469-L505","documentation":"Thrown by UserStore.AddLoginAsync when the user already has a UserLoginInfo with the same LoginProvider. The store enforces one login entry per external provider per user, preventing duplicate provider links.","triggerScenarios":"Calling UserManager.AddLoginAsync twice with the same provider for the same user; re-running an external sign-in linking flow; a race where the same association is applied twice.","commonSituations":"Double-submitting a link-account form; replaying external authentication callbacks; migrating users where provider links were already imported.","solutions":["Check user.LoginInfos (or UserManager.GetLoginsAsync) for the provider before adding.","Catch InvalidOperationException and treat the duplicate link as a no-op/success.","Ensure callback endpoints are idempotent (e.g. redirect after POST, disable double submit).","Deduplicate existing user documents during migration before adding logins."],"exampleFix":"// before\nawait _userManager.AddLoginAsync(user, info);\n// after\nvar logins = await _userManager.GetLoginsAsync(user);\nif (!logins.Any(l => l.LoginProvider == info.LoginProvider))\n{\n    await _userManager.AddLoginAsync(user, info);\n}","handlingStrategy":"try-catch","validationCode":"var logins = await _userManager.GetLoginsAsync(user);\nbool alreadyLinked = logins.Any(l => l.LoginProvider == info.LoginProvider);","typeGuard":"bool HasProvider(IEnumerable<UserLoginInfo> logins, string provider) => logins?.Any(l => l.LoginProvider == provider) == true;","tryCatchPattern":"try { await _userManager.AddLoginAsync(user, info); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"already linked\"))\n{ _logger.LogInformation(\"Login already linked for {User}\", user.UserName); }","preventionTips":["Make external-callback linking idempotent","Prevent double form submission (PRG pattern, disable button)","Deduplicate before migrating logins","Check GetLoginsAsync before every AddLoginAsync"],"tags":["users","external-login","identity"],"backgroundTag":"invalid-state-transition","analyzedSha":"4306c0717fe573f6fca1b4955909ddab6a192807","analyzedAt":"2026-09-13T17:41:05.024Z","contentChangedAt":"2026-09-13T17:41:05.024Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}