public async Task <IActionResult> Login(LoginInputModel model) { var returnUrl = model.ReturnUrl; var context = await _interaction.GetAuthorizationContextAsync(returnUrl); var requires2Fa = context?.AcrValues.Count(t => t.Contains("mfa")) >= 1; var user = await _userManager.FindByNameAsync(model.Email); if (user != null && !user.TwoFactorEnabled && requires2Fa) { return(RedirectToAction(nameof(ErrorEnable2FA))); } ViewData["ReturnUrl"] = returnUrl; if (ModelState.IsValid) { // This doesn't count login failures towards account lockout // To enable password failures to trigger account lockout, set lockoutOnFailure: true var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberLogin, lockoutOnFailure : false); if (result.Succeeded) { _logger.LogInformation(1, "User logged in."); return(RedirectToLocal(returnUrl)); } if (result.RequiresTwoFactor) { var fido2ItemExistsForUser = await _fido2Storage.GetCredentialsByUsername(model.Email); if (fido2ItemExistsForUser.Count > 0) { return(RedirectToAction(nameof(LoginFido2Mfa), new { ReturnUrl = returnUrl, RememberMe = model.RememberLogin })); } else { return(RedirectToAction(nameof(VerifyCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberLogin })); } } if (result.IsLockedOut) { _logger.LogWarning(2, "User account locked out."); return(View("Lockout")); } else { ModelState.AddModelError(string.Empty, _sharedLocalizer["INVALID_LOGIN_ATTEMPT"]); return(View(await BuildLoginViewModelAsync(model))); } } // If we got this far, something failed, redisplay form return(View(await BuildLoginViewModelAsync(model))); }
public async Task <IActionResult> OnPostAsync(string returnUrl = null) { returnUrl = returnUrl ?? Url.Content("~/"); if (ModelState.IsValid) { // This doesn't count login failures towards account lockout // To enable password failures to trigger account lockout, set lockoutOnFailure: true var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure : true); if (result.Succeeded) { _logger.LogInformation("User logged in."); return(LocalRedirect(returnUrl)); } if (result.RequiresTwoFactor) { var fido2ItemExistsForUser = await _fido2Storage.GetCredentialsByUsername(Input.Email); if (fido2ItemExistsForUser.Count > 0) { return(RedirectToPage("./LoginFido2Mfa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe })); } else { return(RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe })); } } if (result.IsLockedOut) { _logger.LogWarning("User account locked out."); return(RedirectToPage("./Lockout")); } else { ModelState.AddModelError(string.Empty, "Invalid login attempt."); return(Page()); } } // If we got this far, something failed, redisplay form return(Page()); }
public async Task <JsonResult> MakeCredentialOptions([FromForm] string username, [FromForm] string displayName, [FromForm] string attType, [FromForm] string authType, [FromForm] bool requireResidentKey, [FromForm] string userVerification) { try { if (string.IsNullOrEmpty(username)) { username = $"{displayName} (Usernameless user created at {DateTime.UtcNow})"; } var identityUser = await _userManager.FindByEmailAsync(username); var user = new Fido2User { DisplayName = identityUser.UserName, Name = identityUser.UserName, Id = Encoding.UTF8.GetBytes(identityUser.UserName) // byte representation of userID is required }; // 2. Get user existing keys by username var items = await _fido2Storage.GetCredentialsByUsername(identityUser.UserName); var existingKeys = new List <PublicKeyCredentialDescriptor>(); foreach (var publicKeyCredentialDescriptor in items) { existingKeys.Add(publicKeyCredentialDescriptor.Descriptor); } // 3. Create options var authenticatorSelection = new AuthenticatorSelection { RequireResidentKey = requireResidentKey, UserVerification = userVerification.ToEnum <UserVerificationRequirement>() }; if (!string.IsNullOrEmpty(authType)) { authenticatorSelection.AuthenticatorAttachment = authType.ToEnum <AuthenticatorAttachment>(); } var exts = new AuthenticationExtensionsClientInputs() { Extensions = true, UserVerificationIndex = true, Location = true, UserVerificationMethod = true, BiometricAuthenticatorPerformanceBounds = new AuthenticatorBiometricPerfBounds { FAR = float.MaxValue, FRR = float.MaxValue } }; var options = _lib.RequestNewCredential(user, existingKeys, authenticatorSelection, attType.ToEnum <AttestationConveyancePreference>(), exts); // 4. Temporarily store options, session/in-memory cache/redis/db //HttpContext.Session.SetString("fido2.attestationOptions", options.ToJson()); var uniqueId = Guid.NewGuid().ToString(); UniqueId = uniqueId; await _distributedCache.SetStringAsync(uniqueId, options.ToJson()); // 5. return options to client return(Json(options)); } catch (Exception e) { return(Json(new CredentialCreateOptions { Status = "error", ErrorMessage = FormatException(e) })); } }
public async Task <IActionResult> Login(LoginInputModel model, string button) { // check if we are in the context of an authorization request var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); // the user clicked the "cancel" button if (button != "login") { if (context != null) { // 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.GrantConsentAsync(context, ConsentResponse.Denied); // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null if (await _clientStore.IsPkceClientAsync(context.ClientId)) { // if the client is PKCE then we assume it's native, so this change in how to // return the response is for better UX for the end user. return(View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl })); } return(Redirect(model.ReturnUrl)); } else { // since we don't have a valid context, then we just go back to the home page return(Redirect("~/")); } } if (ModelState.IsValid) { var user = await _userManager.FindByNameAsync(model.Username); if (user != null && user.IsEnabled) { var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, model.RememberLogin, lockoutOnFailure : true); if (result.Succeeded) { await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName)); if (context != null) { if (await _clientStore.IsPkceClientAsync(context.ClientId)) { // if the client is PKCE then we assume it's native, so this change in how to // return the response is for better UX for the end user. return(View("Redirect", new RedirectViewModel { RedirectUrl = model.ReturnUrl })); } // we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null return(Redirect(model.ReturnUrl)); } // request for a local page if (Url.IsLocalUrl(model.ReturnUrl)) { return(Redirect(model.ReturnUrl)); } else if (string.IsNullOrEmpty(model.ReturnUrl)) { return(Redirect("~/")); } else { // user might have clicked on a malicious link - should be logged throw new Exception("invalid return URL"); } } else if (result.RequiresTwoFactor) { string twoFactorUrl; var fido2ItemExistsForUser = await _fido2Storage.GetCredentialsByUsername(model.Username); if (fido2ItemExistsForUser.Count > 0) { twoFactorUrl = "~/Identity/Account/LoginFido2Mfa?ReturnUrl={0}"; } else { twoFactorUrl = "~/Identity/Account/LoginWith2fa?ReturnUrl={0}"; } if (context != null || Url.IsLocalUrl(model.ReturnUrl)) { return(Redirect(string.Format(twoFactorUrl, HttpUtility.UrlEncode(model.ReturnUrl)))); } else { return(Redirect(string.Format(twoFactorUrl, HttpUtility.UrlEncode("~/")))); } } } await _events.RaiseAsync(new UserLoginFailureEvent(model.Username, "invalid credentials")); ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage); } // something went wrong, show form with error var vm = await BuildLoginViewModelAsync(model); return(View(vm)); }
public async Task <ActionResult> AssertionOptionsPost([FromForm] string username, [FromForm] string userVerification) { try { var identityUser = await _signInManager.GetTwoFactorAuthenticationUserAsync(); if (identityUser == null) { throw new InvalidOperationException(_sharedLocalizer["FIDO2_UNABLE_TO_LOAD_2FA_AUTHENTICATED_USER"]); } var existingCredentials = new List <PublicKeyCredentialDescriptor>(); if (!string.IsNullOrEmpty(identityUser.UserName)) { var user = new Fido2User { DisplayName = identityUser.UserName, Name = identityUser.UserName, Id = Encoding.UTF8.GetBytes(identityUser.UserName) // byte representation of userID is required }; if (user == null) { throw new ArgumentException(_sharedLocalizer["FIDO2_USERNAME_NOT_REGISTERED"]); } // 2. Get registered credentials from database var items = await _fido2Storage.GetCredentialsByUsername(identityUser.UserName); existingCredentials = items.Select(c => c.Descriptor).ToList(); } var exts = new AuthenticationExtensionsClientInputs() { SimpleTransactionAuthorization = "FIDO", GenericTransactionAuthorization = new TxAuthGenericArg { ContentType = "text/plain", Content = new byte[] { 0x46, 0x49, 0x44, 0x4F } }, UserVerificationIndex = true, Location = true, UserVerificationMethod = true }; // 3. Create options var uv = string.IsNullOrEmpty(userVerification) ? UserVerificationRequirement.Discouraged : userVerification.ToEnum <UserVerificationRequirement>(); var options = _lib.GetAssertionOptions( existingCredentials, uv, exts ); // 4. Temporarily store options, session/in-memory cache/redis/db HttpContext.Session.SetString("fido2.assertionOptions", options.ToJson()); // 5. Return options to client return(Json(options)); } catch (Exception e) { return(Json(new AssertionOptions { Status = "error", ErrorMessage = FormatException(e) })); } }