#nullable disable using Wishlist.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity.UI.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.WebUtilities; using System.ComponentModel.DataAnnotations; using System.Security.Claims; using System.Text; using System.Text.Encodings.Web; namespace Wishlist.Areas.Identity.Pages.Account { [AllowAnonymous] public class ExternalLoginModel : PageModel { private readonly SignInManager _signInManager; private readonly UserManager _userManager; private readonly IUserStore _userStore; private readonly IUserEmailStore _emailStore; private readonly IEmailSender _emailSender; private readonly ILogger _logger; public ExternalLoginModel( SignInManager signInManager, UserManager userManager, IUserStore userStore, ILogger logger, IEmailSender emailSender) { _signInManager = signInManager; _userManager = userManager; _userStore = userStore; _emailStore = GetEmailStore(); _logger = logger; _emailSender = emailSender; } /// /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// [BindProperty] public InputModel Input { get; set; } /// /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public string ProviderDisplayName { get; set; } /// /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public string ReturnUrl { get; set; } /// /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// [TempData] public string ErrorMessage { get; set; } /// /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// public class InputModel { /// /// This API supports the ASP.NET Core Identity default UI infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// [Required] [EmailAddress] public string Email { get; set; } } public IActionResult OnGet() => RedirectToPage("./Login"); public IActionResult OnPost(string provider, string returnUrl = null) { // Request a redirect to the external login provider. var redirectUrl = Url.Page("./ExternalLogin", pageHandler: "Callback", values: new { returnUrl }); var properties = _signInManager.ConfigureExternalAuthenticationProperties(provider, redirectUrl); return new ChallengeResult(provider, properties); } public async Task OnGetCallbackAsync(string returnUrl = null, string remoteError = null) { returnUrl = returnUrl ?? Url.Content("~/"); if (remoteError != null) { ErrorMessage = $"Error from external provider: {remoteError}"; return RedirectToPage("./Login", new { ReturnUrl = returnUrl }); } var info = await _signInManager.GetExternalLoginInfoAsync(); if (info == null) { ErrorMessage = "Error loading external login information."; return RedirectToPage("./Login", new { ReturnUrl = returnUrl }); } // 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, bypassTwoFactor: true); if (result.Succeeded) { _logger.LogInformation("{Name} logged in with {LoginProvider} provider.", info.Principal.Identity.Name, info.LoginProvider); return LocalRedirect(returnUrl); } if (result.IsLockedOut) { return RedirectToPage("./Lockout"); } else { // If the user does not have an account, then ask the user to create an account. ReturnUrl = returnUrl; ProviderDisplayName = info.ProviderDisplayName; if (info.Principal.HasClaim(c => c.Type == ClaimTypes.Email)) { Input = new InputModel { Email = info.Principal.FindFirstValue(ClaimTypes.Email) }; } return Page(); } } public async Task OnPostConfirmationAsync(string returnUrl = null) { returnUrl = returnUrl ?? Url.Content("~/"); // Get the information about the user from the external login provider var info = await _signInManager.GetExternalLoginInfoAsync(); if (info == null) { ErrorMessage = "Error loading external login information during confirmation."; return RedirectToPage("./Login", new { ReturnUrl = returnUrl }); } if (ModelState.IsValid) { var user = await CreateUser(); // fill user info (name, givenname, surname ...) FillExternalProviderUserInfo(user, info); await _userStore.SetUserNameAsync(user, Input.Email, CancellationToken.None); await _emailStore.SetEmailAsync(user, Input.Email, CancellationToken.None); var result = await _userManager.CreateAsync(user); if (result.Succeeded) { result = await _userManager.AddLoginAsync(user, info); if (result.Succeeded) { _logger.LogInformation("User created an account using {Name} provider.", info.LoginProvider); // Add User role await _userManager.AddToRoleAsync(user, "User"); var userId = await _userManager.GetUserIdAsync(user); var code = await _userManager.GenerateEmailConfirmationTokenAsync(user); code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); var callbackUrl = Url.Page( "/Account/ConfirmEmail", pageHandler: null, values: new { area = "Identity", userId = userId, code = code }, protocol: Request.Scheme); await _emailSender.SendEmailAsync(Input.Email, "Confirm your email", $"Please confirm your account by clicking here."); // If account confirmation is required, we need to show the link if we don't have a real email sender if (_userManager.Options.SignIn.RequireConfirmedAccount) { return RedirectToPage("./RegisterConfirmation", new { Email = Input.Email }); } await _signInManager.SignInAsync(user, isPersistent: false, info.LoginProvider); return LocalRedirect(returnUrl); } } foreach (var error in result.Errors) { ModelState.AddModelError(string.Empty, error.Description); } } ProviderDisplayName = info.ProviderDisplayName; ReturnUrl = returnUrl; return Page(); } public static class SupportedExternalLoginProviderNames { public const string GOOGLE = "Google"; public const string FACEBOOK = "Facebook"; public const string MICROSOFT = "Microsoft"; //public const string TWITTER = "Twitter"; } private void FillExternalProviderUserInfo(ApplicationUser user, ExternalLoginInfo info) { if (info is null || string.IsNullOrEmpty(info.LoginProvider)) return; switch (info.LoginProvider) { case SupportedExternalLoginProviderNames.GOOGLE: Claim gname = info.Principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Name); if (gname is not null) user.Name = gname.Value; Claim ggivenName = info.Principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName); if (ggivenName is not null) user.GivenName = ggivenName.Value; Claim gsurname = info.Principal.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname); if (gsurname is not null) user.Surname = gsurname.Value; break; case SupportedExternalLoginProviderNames.FACEBOOK: Claim fname = info.Principal.Claims.FirstOrDefault(c => c.Type == "urn:facebook:name"); if (fname is not null) user.Name = fname.Value; Claim fgivenName = info.Principal.Claims.FirstOrDefault(c => c.Type == "urn:facebook:first_name"); if (fgivenName is not null) user.Name = fgivenName.Value; Claim fsurname = info.Principal.Claims.FirstOrDefault(c => c.Type == "urn:facebook:last_name"); if (fsurname is not null) user.Name = fsurname.Value; break; case SupportedExternalLoginProviderNames.MICROSOFT: Claim mname = info.Principal.Claims.FirstOrDefault(c => c.Type == "displayName"); if (mname is not null) user.Name = mname.Value; Claim mgivenName = info.Principal.Claims.FirstOrDefault(c => c.Type == "first_name"); if (mgivenName is not null) user.GivenName = mgivenName.Value; Claim msurname = info.Principal.Claims.FirstOrDefault(c => c.Type == "last_name"); if (msurname is not null) user.Surname = msurname.Value; break; //case SupportedExternalLoginProviderNames.TWITTER: // Claim tname = info.Principal.Claims.FirstOrDefault(c => c.Type == "screen_name"); // if (tname is not null) // user.Name = tname.Value; // break; default: break; } } private async Task CreateUser() { try { var user = Activator.CreateInstance(); await user.SaveToDb(); return user; } catch { throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " + $"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor, or alternatively " + $"override the external login page in /Areas/Identity/Pages/Account/ExternalLogin.cshtml"); } } private IUserEmailStore GetEmailStore() { if (!_userManager.SupportsUserEmail) { throw new NotSupportedException("The default UI requires a user store with email support."); } return (IUserEmailStore)_userStore; } } }