private async Task <SignInResult> LinkUser(MemberIdentityUser autoLinkUser, ExternalLoginInfo loginInfo) { IList <UserLoginInfo>?existingLogins = await UserManager.GetLoginsAsync(autoLinkUser); UserLoginInfo?exists = existingLogins.FirstOrDefault(x => x.LoginProvider == loginInfo.LoginProvider && x.ProviderKey == loginInfo.ProviderKey); // if it already exists (perhaps it was added in the AutoLink callbak) then we just continue if (exists != null) { // sign in return(await SignInOrTwoFactorAsync(autoLinkUser, false, loginInfo.LoginProvider)); } IdentityResult?linkResult = await UserManager.AddLoginAsync(autoLinkUser, loginInfo); if (linkResult.Succeeded) { // we're good! sign in return(await SignInOrTwoFactorAsync(autoLinkUser, false, loginInfo.LoginProvider)); } // If this fails, we should really delete the user since it will be in an inconsistent state! IdentityResult?deleteResult = await UserManager.DeleteAsync(autoLinkUser); if (deleteResult.Succeeded) { var errors = linkResult.Errors.Select(x => x.Description).ToList(); return(AutoLinkSignInResult.FailedLinkingUser(errors)); } else { // DOH! ... this isn't good, combine all errors to be shown var errors = linkResult.Errors.Concat(deleteResult.Errors).Select(x => x.Description).ToList(); return(AutoLinkSignInResult.FailedLinkingUser(errors)); } }
/// <summary> /// Used for auto linking/creating user accounts for external logins /// </summary> /// <param name="loginInfo"></param> /// <param name="autoLinkOptions"></param> /// <returns></returns> private async Task <SignInResult> AutoLinkAndSignInExternalAccount( ExternalLoginInfo loginInfo, MemberExternalSignInAutoLinkOptions?autoLinkOptions) { // If there are no autolink options then the attempt is failed (user does not exist) if (autoLinkOptions == null || !autoLinkOptions.AutoLinkExternalAccount) { return(SignInResult.Failed); } var email = loginInfo.Principal.FindFirstValue(ClaimTypes.Email); // we are allowing auto-linking/creating of local accounts if (email.IsNullOrWhiteSpace()) { return(AutoLinkSignInResult.FailedNoEmail); } // Now we need to perform the auto-link, so first we need to lookup/create a user with the email address MemberIdentityUser?autoLinkUser = await UserManager.FindByEmailAsync(email); if (autoLinkUser != null) { try { // call the callback if one is assigned autoLinkOptions.OnAutoLinking?.Invoke(autoLinkUser, loginInfo); } catch (Exception ex) { Logger.LogError(ex, "Could not link login provider {LoginProvider}.", loginInfo.LoginProvider); return(AutoLinkSignInResult.FailedException(ex.Message)); } var shouldLinkUser = autoLinkOptions.OnExternalLogin == null || autoLinkOptions.OnExternalLogin(autoLinkUser, loginInfo); if (shouldLinkUser) { return(await LinkUser(autoLinkUser, loginInfo)); } LogFailedExternalLogin(loginInfo, autoLinkUser); return(ExternalLoginSignInResult.NotAllowed); } var name = loginInfo.Principal?.Identity?.Name; if (name.IsNullOrWhiteSpace()) { throw new InvalidOperationException("The Name value cannot be null"); } autoLinkUser = MemberIdentityUser.CreateNew(email, email, autoLinkOptions.DefaultMemberTypeAlias, autoLinkOptions.DefaultIsApproved, name); foreach (var userGroup in autoLinkOptions.DefaultMemberGroups) { autoLinkUser.AddRole(userGroup); } // call the callback if one is assigned try { autoLinkOptions.OnAutoLinking?.Invoke(autoLinkUser, loginInfo); } catch (Exception ex) { Logger.LogError(ex, "Could not link login provider {LoginProvider}.", loginInfo.LoginProvider); return(AutoLinkSignInResult.FailedException(ex.Message)); } IdentityResult?userCreationResult = await UserManager.CreateAsync(autoLinkUser); if (!userCreationResult.Succeeded) { return(AutoLinkSignInResult.FailedCreatingUser( userCreationResult.Errors.Select(x => x.Description).ToList())); } { var shouldLinkUser = autoLinkOptions.OnExternalLogin == null || autoLinkOptions.OnExternalLogin(autoLinkUser, loginInfo); if (shouldLinkUser) { return(await LinkUser(autoLinkUser, loginInfo)); } LogFailedExternalLogin(loginInfo, autoLinkUser); return(ExternalLoginSignInResult.NotAllowed); } }