SignInManager<ApplicationUser>.GetExternalLoginInfoAsync returns null. I am not sure why. I am trying to retrieve the same"userId" (user-defined) that I stored in the cache via "OnAuthorizationCodeReceived":
public async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context) { // string userId = context.Ticket.Principal.FindFirst(ClaimTypes.NameIdentifier).Value; // string userId = context.HttpContext.User.Identity.Name; string userId = context.Principal. FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value; // Use MSAL to swap the code for an access token // Extract the code from the response notification string code = context.ProtocolMessage.Code; TokenCache tokenCache = new SessionTokenCache(userId, context.HttpContext).GetMsalCacheInstance(); ClientCredential clientCred = new ClientCredential(azureadclientsecret); ConfidentialClientApplication cca = new ConfidentialClientApplication(azureadclientid, string.Format(CultureInfo.InvariantCulture, azureadaadinstance, "common", "/v2.0"), oauth2redirecturi, clientCred, tokenCache, null); try { AuthenticationResult result = await cca.AcquireTokenByAuthorizationCodeAsync(code, oauth2appscopes.Split(' ')); context.HandleCodeRedemption(result.AccessToken, result.IdToken); } catch { AuthenticationResult result = await cca.AcquireTokenSilentAsync(oauth2appscopes.Split(' '), cca.Users.First()); // throw; } }
In the following task where the app gets the access token, SignInManager<ApplicationUser>.GetExternalLoginInfoAsync returns null (need to get the "ExternalLoginInfo", so that I can get the "userId" (user-defined) from it):
public async Task<string> GetAccessToken() { string accessToken = null; // Load the app config from web.config string appId = _oauth2.AppId; string appPassword = _oauth2.AppPassword; string redirectUri = _oauth2.RedirectUri; string[] appScopes = _oauth2.AppScopes .Replace(' ', ',').Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); ExternalLoginInfo info = await _signInManager.GetExternalLoginInfoAsync(/*userId*/); // ClaimsPrincipal principal = User as ClaimsPrincipal; // ClaimsIdentity principalIdentity = principal.Identity as ClaimsIdentity; string userId = info.Principal.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value; // Get the current user's ID // string userId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value; // string userId = principalIdentity.FindFirst(ClaimTypes.NameIdentifier).Value; // string userId = principal.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value; if (!string.IsNullOrEmpty(userId)) { // Get the user's token cache Microsoft.Identity.Client.TokenCache tokenCache = new SessionTokenCache(userId, HttpContext).GetMsalCacheInstance(); // Microsoft.Identity.Client.TokenCache tokenCache = new NaiveSessionCache(userId, HttpContext.Session); ConfidentialClientApplication cca = new ConfidentialClientApplication( appId, redirectUri, new Microsoft.Identity.Client.ClientCredential(appPassword), tokenCache, null); Microsoft.Identity.Client.AuthenticationResult result = await cca.AcquireTokenSilentAsync(appScopes, cca.Users.First()); if (result != null) { accessToken = result.AccessToken; } } return accessToken; }
After logging into an account with an external login, I tried accessing the inbox of the external login, but it is not working, ultimately because of SignInManager<ApplicationUser>.GetExternalLoginInfoAsync returning null. I do not know how I can get the "userId" (user-defined) another way.
It appears that the only times that SignInManager<ApplicationUser>.GetExternalLoginInfoAsync DOES NOT return null is in the following two functions, which are callback functions that execute after a user logs in via an external login provider:
// // GET: /Manage/LinkLoginCallback [HttpGet] public async Task<ActionResult> LinkLoginCallback() { try { var user = await GetCurrentUserAsync(); if (user == null) { return View("Error"); } var info = await _signInManager.GetExternalLoginInfoAsync(/*_userManager.GetUserId(User)*/); if (info.LoginProvider == "" || info.Principal.FindFirst("preferred_username").Value == "") { return RedirectToAction(nameof(ManageLogins), new { Message = ManageMessageId.EmptyError }); } var result = await _userManager.AddLoginAsync(user, info); var message = ManageMessageId.Error; if (result.Succeeded) { var code = await _userManager.GeneratePasswordResetTokenAsync(user); var callbackUrl = Url.Action("ResetPassword", "Account", new { userId = user.Id, code = code }, protocol: HttpContext.Request.Scheme); string emailSubjectToVisitor = "A(n) " + info.LoginProvider + " account (UserName: " + info.Principal.FindFirst("preferred_username").Value + ") has been added to your " +"globalwarming.center account (UserName: " + user.UserName + ")"; string emailMessageToVisitor = "<p>Dear " + user.UserName + ":</p><p>" +"A(n) " + info.LoginProvider + " account (UserName: " + info.Principal.FindFirst("preferred_username").Value + ") has been added to your " +"globalwarming.center account (UserName: " + user.UserName + ").</p><p>If you did " +"not add this account, someone else may have access to your globalwarming.center " +"account. You should immediately reset your password by clicking this " +$"<a href='{callbackUrl}'>link</a> and filling out the form. That way, you can " +"change your password and prevent anyone else from logging into your " +"account. Then, you should log into your account and remove the " + info.LoginProvider + " account, if necessary.</p><p>" +"Thank you." + "</p><p>" +"Sincerely," + "<br>" + _pointOfContact.FullName + "<br>" + _pointOfContact.Title + "</p>"; await _emailSender.SendEmailAsync(user.Email, emailSubjectToVisitor, emailMessageToVisitor); message = ManageMessageId.AddLoginSuccess; // Clear the existing external cookie to ensure a clean login process await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme); return RedirectToAction(nameof(ManageLogins), new { Message = ManageMessageId.AddLoginSuccess }); } else { string error = result.Errors.First().Description; return RedirectToAction(nameof(ManageLogins), new { Message = message, Error = error }); } } catch (Exception ex) { return RedirectToAction(nameof(ManageLogins), new { Message = ManageMessageId.Error, Error = ex.Message }); } }
// // GET: /Account/ExternalLoginCallback [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)); } ExternalLoginInfo info = await _signInManager.GetExternalLoginInfoAsync(/*_userManager.GetUserId(User)*/); if (info == null) { // return RedirectToAction(nameof(Login)); ModelState.AddModelError(string.Empty, "Could not get external login info"); return View(nameof(Login)); } // Sign in the user with this external login provider if the user already has a login. 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; ClaimsIdentity principalIdentity = info.Principal.Identity as ClaimsIdentity; string preferredUsername = principalIdentity.FindFirst("preferred_username").Value; ViewData["PreferredUsername"] = preferredUsername; return View("ExternalLoginConfirmation", new ExternalLoginConfirmationViewModel { Email = preferredUsername }); } }
Here is the link to my GitHub repository.
Thank you.