Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- using Duende.IdentityServer.Events;
- using Duende.IdentityServer.Models;
- using Duende.IdentityServer.Services;
- using Duende.IdentityServer.Stores;
- using HeadhuntNow.Auth.Models;
- using Microsoft.AspNetCore.Authentication;
- using Microsoft.AspNetCore.Authorization;
- using Microsoft.AspNetCore.Identity;
- using Microsoft.AspNetCore.Mvc;
- using Microsoft.AspNetCore.Mvc.RazorPages;
- namespace HeadhuntNow.Auth.Pages.Login;
- [SecurityHeaders]
- [AllowAnonymous]
- public class Index : PageModel
- {
- private readonly UserManager<ApplicationUser> _userManager;
- private readonly SignInManager<ApplicationUser> _signInManager;
- private readonly IIdentityServerInteractionService _interaction;
- private readonly IEventService _events;
- private readonly IAuthenticationSchemeProvider _schemeProvider;
- private readonly IIdentityProviderStore _identityProviderStore;
- public ViewModel View { get; set; }
- [BindProperty] public InputModel Input { get; set; }
- public Index (
- IIdentityServerInteractionService interaction,
- IAuthenticationSchemeProvider schemeProvider,
- IIdentityProviderStore identityProviderStore,
- IEventService events,
- UserManager<ApplicationUser> userManager,
- SignInManager<ApplicationUser> signInManager)
- {
- _userManager = userManager;
- _signInManager = signInManager;
- _interaction = interaction;
- _schemeProvider = schemeProvider;
- _identityProviderStore = identityProviderStore;
- _events = events;
- }
- public async Task<IActionResult> OnGet (string returnUrl)
- {
- await BuildModelAsync(returnUrl);
- if (View.IsExternalLoginOnly)
- {
- // we only have one option for logging in and it's an external provider
- return RedirectToPage("/ExternalLogin/Challenge", new { scheme = View.ExternalLoginScheme, returnUrl });
- }
- return Page();
- }
- public async Task<IActionResult> OnPost()
- {
- var context = await _interaction.GetAuthorizationContextAsync(Input.ReturnUrl);
- if (context == null) return Redirect("~/");
- if (Input.Button != "login")
- {
- return await HandleUserCancellation(context);
- }
- if (ModelState.IsValid)
- {
- return await PerformLogin(context);
- }
- // something went wrong, show form with error
- await BuildModelAsync(Input.ReturnUrl);
- return Page();
- }
- private async Task BuildModelAsync (string returnUrl)
- {
- Input = new InputModel
- {
- ReturnUrl = returnUrl
- };
- var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
- if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null)
- {
- var local = context.IdP == Duende.IdentityServer.IdentityServerConstants.LocalIdentityProvider;
- // this is meant to short circuit the UI and only trigger the one external IdP
- View = new ViewModel
- {
- EnableLocalLogin = local,
- };
- Input.Username = context?.LoginHint;
- if (!local)
- {
- View.ExternalProviders = new[] { new ViewModel.ExternalProvider { AuthenticationScheme = context?.IdP } };
- }
- return;
- }
- var schemes = await _schemeProvider.GetAllSchemesAsync();
- var providers = schemes
- .Where(x => x.DisplayName != null)
- .Select(
- x => new ViewModel.ExternalProvider
- {
- DisplayName = x.DisplayName ?? x.Name,
- AuthenticationScheme = x.Name
- }).ToList();
- var dyanmicSchemes = (await _identityProviderStore.GetAllSchemeNamesAsync())
- .Where(x => x.Enabled)
- .Select(
- x => new ViewModel.ExternalProvider
- {
- AuthenticationScheme = x.Scheme,
- DisplayName = x.DisplayName
- });
- providers.AddRange(dyanmicSchemes);
- var allowLocal = true;
- var client = context?.Client;
- if (client != null)
- {
- allowLocal = client.EnableLocalLogin;
- if (client.IdentityProviderRestrictions != null && client.IdentityProviderRestrictions.Any())
- {
- providers = providers.Where(provider => client.IdentityProviderRestrictions.Contains(provider.AuthenticationScheme)).ToList();
- }
- }
- View = new ViewModel
- {
- AllowRememberLogin = LoginOptions.AllowRememberLogin,
- EnableLocalLogin = allowLocal && LoginOptions.AllowLocalLogin,
- ExternalProviders = providers.ToArray()
- };
- }
- private async Task<IActionResult> HandleUserCancellation(AuthorizationRequest context)
- {
- if (context == null) return Redirect("~/");
- // if the user cancels, send a result back into IdentityServer as if they
- // denied the consent (even if this client does not require consent).
- // this will send back an access denied OIDC error response to the client.
- await _interaction.DenyAuthorizationAsync(context, AuthorizationError.AccessDenied);
- // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
- return context.IsNativeClient() ?
- // The client is native, so this change in how to return the response is for better UX for the end user.
- this.LoadingPage(Input.ReturnUrl) : Redirect(Input.ReturnUrl);
- // since we don't have a valid context, then we just go back to the home page
- }
- private async Task<IActionResult> PerformLogin(AuthorizationRequest context)
- {
- var user = await _userManager.FindByNameAsync(Input.Username);
- if (user == null)
- {
- // If username is not found, try to find by email
- user = await _userManager.FindByEmailAsync(Input.Username);
- if (user == null)
- {
- ModelState.AddModelError(string.Empty, "Invalid user");
- await BuildModelAsync(Input.ReturnUrl);
- return Page();
- }
- }
- if (!user.EmailConfirmed)
- {
- TempData["UserId"] = user.Id;
- // Redirect to a confirmation page.
- return RedirectToPage("/Account/ResendConfirm");
- }
- if (user.UserName is null)
- {
- ModelState.AddModelError(string.Empty, "Invalid user");
- return Page();
- }
- var result = await _signInManager.PasswordSignInAsync(user.UserName, Input.Password, Input.RememberLogin, lockoutOnFailure: true);
- if (result.Succeeded)
- {
- return await HandleLoginResult(context, user, result);
- }
- // Add error and raise event in case of invalid credentials
- ModelState.AddModelError(string.Empty, LoginOptions.InvalidCredentialsErrorMessage);
- await _events.RaiseAsync(new UserLoginFailureEvent(Input.Username, "invalid credentials", clientId: context?.Client.ClientId));
- await BuildModelAsync(Input.ReturnUrl);
- return Page();
- }
- private async Task<IActionResult> HandleLoginResult(AuthorizationRequest context, ApplicationUser user, Microsoft.AspNetCore.Identity.SignInResult result)
- {
- if (result.Succeeded)
- {
- await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId));
- if (context != null)
- {
- return context.IsNativeClient()
- ?
- // The client is native, so this change in how to
- // return the response is for better UX for the end user.
- this.LoadingPage(Input.ReturnUrl)
- :
- // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
- Redirect(Input.ReturnUrl);
- }
- // request for a local page
- if (Url.IsLocalUrl(Input.ReturnUrl))
- {
- return Redirect(Input.ReturnUrl);
- }
- if (string.IsNullOrEmpty(Input.ReturnUrl))
- {
- return Redirect("~/");
- }
- // throw new Exception("invalid return URL"); // user clicked a malicious link
- // Logger.LogWarning("Invalid return URL redirect attempted to '{ReturnUrl}'. A potential Open Redirect Attack was detected and mitigation action was taken by redirecting user to the base/home URL.", Input.ReturnUrl);
- return Redirect("~/");
- }
- if (result.RequiresTwoFactor)
- {
- //TODO: Implement 2FA
- // return RedirectToPage("./LoginWith2fa", new { ReturnUrl = Input.ReturnUrl, RememberMe = Input.RememberLogin });
- }
- if (result.IsLockedOut)
- {
- //TODO: Implement lockout
- return RedirectToPage("/Account/Lockedout");
- }
- return null;
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment