| File: Areas\Identity\Pages\V4\Account\ExternalLogin.cshtml.cs | Web Access |
| Project: src\aspnetcore\src\Identity\UI\src\Microsoft.AspNetCore.Identity.UI.csproj (Microsoft.AspNetCore.Identity.UI) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.ComponentModel.DataAnnotations; using System.Diagnostics.CodeAnalysis; using System.Security.Claims; using System.Text; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal; /// <summary> /// 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. /// </summary> [AllowAnonymous] [IdentityDefaultUI(typeof(ExternalLoginModel<>))] public class ExternalLoginModel : PageModel { /// <summary> /// 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. /// </summary> [BindProperty] public InputModel Input { get; set; } = default!; /// <summary> /// 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. /// </summary> public string? ProviderDisplayName { get; set; } /// <summary> /// 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. /// </summary> public string? ReturnUrl { get; set; } /// <summary> /// 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. /// </summary> [TempData] public string? ErrorMessage { get; set; } /// <summary> /// 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. /// </summary> public class InputModel { /// <summary> /// 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. /// </summary> [Required] [EmailAddress] public string Email { get; set; } = default!; } /// <summary> /// 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. /// </summary> public virtual IActionResult OnGet() => throw new NotImplementedException(); /// <summary> /// 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. /// </summary> public virtual IActionResult OnPost(string provider, [StringSyntax(StringSyntaxAttribute.Uri)] string? returnUrl = null) => throw new NotImplementedException(); /// <summary> /// 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. /// </summary> public virtual Task<IActionResult> OnGetCallbackAsync([StringSyntax(StringSyntaxAttribute.Uri)] string? returnUrl = null, string? remoteError = null) => throw new NotImplementedException(); /// <summary> /// 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. /// </summary> public virtual Task<IActionResult> OnPostConfirmationAsync([StringSyntax(StringSyntaxAttribute.Uri)] string? returnUrl = null) => throw new NotImplementedException(); } internal sealed class ExternalLoginModel<TUser> : ExternalLoginModel where TUser : class { private readonly SignInManager<TUser> _signInManager; private readonly UserManager<TUser> _userManager; private readonly IUserStore<TUser> _userStore; private readonly IUserEmailStore<TUser> _emailStore; private readonly IEmailSender<TUser> _emailSender; private readonly ILogger<ExternalLoginModel> _logger; public ExternalLoginModel( SignInManager<TUser> signInManager, UserManager<TUser> userManager, IUserStore<TUser> userStore, ILogger<ExternalLoginModel> logger, IEmailSender<TUser> emailSender) { _signInManager = signInManager; _userManager = userManager; _userStore = userStore; _emailStore = GetEmailStore(); _logger = logger; _emailSender = emailSender; } public override IActionResult OnGet() => RedirectToPage("./Login"); public override 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 override async Task<IActionResult> 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) { if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation(LoggerEventIds.UserLoggedInByExternalProvider, "User logged in with {LoginProvider} provider.", 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 override async Task<IActionResult> 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 = CreateUser(); 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) { if (_logger.IsEnabled(LogLevel.Information)) { _logger.LogInformation(LoggerEventIds.UserCreatedByExternalProvider, "User created an account using {Name} provider.", info.LoginProvider); } 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.SendConfirmationLinkAsync(user, Input.Email, HtmlEncoder.Default.Encode(callbackUrl)); // 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(); } private TUser CreateUser() { try { return Activator.CreateInstance<TUser>(); } catch { throw new InvalidOperationException($"Can't create an instance of '{nameof(TUser)}'. " + $"Ensure that '{nameof(TUser)}' 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<TUser> GetEmailStore() { if (!_userManager.SupportsUserEmail) { throw new NotSupportedException("The default UI requires a user store with email support."); } return (IUserEmailStore<TUser>)_userStore; } }