Link your new Google or Facebook account to your existing account

I just followed the instructions in this article to add Google as the login provider for my MVC 5 app. Everything seems to work fine, but when I log in via Google, it wants me to register an email address / username, provided by Google as a new account in your application. If I stay by e-mail as it is, and click the "Register" button, it informs me that the address is already taken, since I previously registered in my own application to log into the system.

How can I customize the default code generated by the MVC project template so that I can associate a Google login with an existing local account?

PS I have exactly the same problem with Facebook.

+8
facebook asp.net-mvc oauth asp.net-mvc-5 google-oauth2
source share
6 answers

I think that Identity handles it the way it is done on purpose, since there is no real way to verify the user's identity by email coming from a third party. Although the risk can be relatively low, it is possible that someone can create an account with a third party, such as Facebook, using an email address that they do not own, and then use this third-party login to impersonate an account on another website attached to the same email.

As a result, Identity allows you to create a new account with an external login at login, rather than connecting to an existing one. However, as soon as the user authenticates in other ways, methods are provided for linking additional logins.

If you are not worried about the relatively mild security risk associated with the fact that if the email address matches it with the same person, you only need to change the ExternalLoginCallback in AccountController.cs to try to find the user by email:

 var user = await UserManager.FindByEmailAsync(loginInfo.Email); 

And then sign them in:

 await SignInManager.SignInAsync(user); 
+4
source share

I completely pushed the points raised by @Chris Pratt However, I'm not sure if the code used is enough to do what the OP asked.

adding this code to the default block inside ExternalLoginCallback should do the job

 ApplicationUser user = await UserManager.FindByNameAsync(loginInfo.Email); if (user != null) { var addLoginResult = await UserManager.AddLoginAsync(user.Id, loginInfo.Login); if (addLoginResult.Succeeded) { await SignInManager.SignInAsync(user, isPersistent: false, rememberBrowser: false); return RedirectToLocal(returnUrl); } } 
+7
source share

This is how I was able to solve this problem that you have. This solution will allow you to register on the site with your email address, and if you then try to use Google to log in with the same email address, you will not be asked to register and you will not generate any errors; you will be allowed to log in if the email address with which you are logged in matches the email address of your google account. I edited the default ExternalLoginCallBack code, which VS2015 generated using the if / else statement, which checks for an existing email that matches the login email. Hope this helps you with your question because I had the same problem and could not find the answer anywhere. My many requests for messages were ignored, and fortunately I read one of the answers from this article that led me to my own solution that works for me on VS2015 Core.

 [HttpGet] [AllowAnonymous] public async Task<IActionResult> ExternalLoginCallback(string returnUrl = null, string remoteError = null) { if (remoteError != null) { ModelState.AddModelError(string.Empty, $"Error from external provider: {remoteError}"); return View(nameof(Login)); } var info = await _signInManager.GetExternalLoginInfoAsync(); if (info == null) { return RedirectToAction(nameof(Login)); } // Sign in the user with this external login provider if the user already has a login. var email = info.Principal.FindFirstValue(ClaimTypes.Email); var user = await _userManager.FindByNameAsync(email); if (user != null) { await _signInManager.SignInAsync(user, isPersistent: false); return RedirectToLocal(returnUrl); } else { // If user does not already exists, invite User to register. var result = await _signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, isPersistent: false); if (result.Succeeded) { _logger.LogInformation(5, "User logged in with {Name} provider.", info.LoginProvider); return RedirectToLocal(returnUrl); } if (result.RequiresTwoFactor) { return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl }); } if (result.IsLockedOut) { return View("Lockout"); } else { // If the user does not have an account, then ask the user to create an account. ViewData["ReturnUrl"] = returnUrl; ViewData["LoginProvider"] = info.LoginProvider; email = info.Principal.FindFirstValue(ClaimTypes.Email); return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = email }); } } } 
+2
source share

This is common behavior in the MVC pattern , as external logins try to create a user, and if a user email (or external authentication) already exists, try logging in. In addition, the external login provider is trying to assign an additional and unique identifier in your application (locally) separately from your external identity. But, as you said, the following is strange :

If I leave the letter as is and click the "Register" button, it tells me that this address is already taken, since I previously registered on my own login application.

Which should work, since each user external identifier should be unique on other sites (I think), if you have not registered several external accounts with the same identifier, since the table structures look like this:

enter image description here

The UserId column will be mapped to the UserId column below:

enter image description here

A message will rarely be displayed when a user tries to assign a duplicate Username , which will be indicated with your email or username on other sites (for example, somename@gmail.com will be some like Username )

+1
source share

TL; DR you need to go through all the scripts manually in your ExternalLoginConfirmation function and have a database table in order to be able to map the user ID to the OAuth user ID. This way you can β€œlink” multiple OAuth accounts to one local account.

Below is a snippet of code from one of our projects - I hope it is clear enough

 public ActionResult ExternalLoginCallback() { var returnUrl = HttpContext.Request.QueryString["returnUrl"]; var result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl })); if (result.IsSuccessful == false) { return this.View("ExternalLoginFailure", result); } // Login user if provider represents a valid already registered user if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false)) { return this.RedirectToLocal(returnUrl); } // If the current user is logged in already - add new account if (User.Identity.IsAuthenticated) { OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, User.Identity.Name); return this.RedirectToLocal(returnUrl); } var membershipUser = Membership.GetUser(result.UserName); // so user is new - then create new membership account if (membershipUser == null) { MembershipCreateStatus createStatus; membershipUser = Membership.CreateUser(username: result.UserName, password: this.GetTempPassword(), email: result.UserName, status: out createStatus); if (createStatus == MembershipCreateStatus.Success) { this.emailService.SendWelcome(this, (Guid)membershipUser.ProviderUserKey); // Associate social network account with created membership account OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, result.UserName); OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false); return this.RedirectToLocal(returnUrl); } // The problem occured while creating membership account this.ViewBag.Error = MembershipErrorNameProvider.FromErrorCode(createStatus); return this.View("CreateMembershipAccountFailure"); } // If membership account already exists -> Associate Social network account with exists membership account OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, result.UserName); OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false); return this.RedirectToLocal(returnUrl); } 

and OAuthWebSecurity is a helper class that applies to all the providers you support:

 public static class OAuthWebSecurity { .... public static bool Login(string providerName, string providerUserId, bool createPersistentCookie) { var context = new HttpContextWrapper(HttpContext.Current); var provider = GetOAuthClient(providerName); var securityManager = new OpenAuthSecurityManager(context, provider, OAuthDataProvider); return securityManager.Login(providerUserId, createPersistentCookie); } public static void CreateOrUpdateAccount(string openAuthProvider, string openAuthId, string userName) { var user = UserRepository.FindByName(userName); if (user == null) { throw new MembershipUserNotFoundException(); } var userOAuthAccount = UserOAuthAccountRepository.Find(openAuthProvider, openAuthId); if (userOAuthAccount == null) { UserOAuthAccountRepository.InsertOrUpdate(new UserOAuthAccount { OAuthProvider = openAuthProvider, OAuthId = openAuthId, UserId = user.Id }); } else { userOAuthAccount.UserId = user.Id; } UserOAuthAccountRepository.Save(); } } 
0
source share

This following link shows how to create an ASP.NET MVC 5 web application that allows users to sign in using OAuth 2.0 with credentials from an external authentication provider such as Facebook, Twitter, LinkedIn, Microsoft, or Google. For simplicity, this guide focuses on working with credentials from existing Facebook and Google accounts.

Note:

MVC 5 app with Facebook, Twitter, LinkedIn and Google OAuth2 Sign-on (C #)

-one
source share

All Articles