public IActionResult AdditionalAuthenticationFactor( string returnUrl, bool rememberLogin) { // create VM var vm = new AdditionalAuthenticationFactorViewModel() { RememberLogin = rememberLogin, ReturnUrl = returnUrl }; return(View(vm)); }
public async Task <IActionResult> AdditionalAuthenticationFactor( AdditionalAuthenticationFactorViewModel model) { // check if we are in the context of an authorization request var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl); if (ModelState.IsValid) { // read identity from the temporary cookie var result = await HttpContext.AuthenticateAsync("idsrv.mfa"); if (result?.Succeeded != true) { throw new Exception("MFA authentication error"); } var subject = result.Principal.FindFirst(JwtClaimTypes.Subject)?.Value; var user = await _localUserService.GetUserBySubjectAsync(subject); var userSecret = await _localUserService.GetUserSecret(subject, "TOTP"); var totpSecret = (userSecret == null) ? _totpSecret : userSecret.Secret; var authenticator = new TwoStepsAuthenticator.TimeAuthenticator(); if (!authenticator.CheckCode(totpSecret, model.Totp, user)) { ModelState.AddModelError("totp", "TOTP is invalid."); return(View(model)); } await _events.RaiseAsync(new UserLoginSuccessEvent( user.Username, user.Subject, user.Username, clientId : context?.ClientId)); // only set explicit expiration here if user chooses "remember me". // otherwise we rely upon expiration configured in cookie middleware. AuthenticationProperties props = null; if (AccountOptions.AllowRememberLogin && model.RememberLogin) { props = new AuthenticationProperties { IsPersistent = true, ExpiresUtc = DateTimeOffset.UtcNow.Add(AccountOptions.RememberMeLoginDuration) }; } ; // issue authentication cookie with subject ID and username await HttpContext.SignInAsync(user.Subject, user.Username, props); // delete temporary cookie used during mfa await HttpContext.SignOutAsync("idsrv.mfa"); 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"); } } return(View(model)); }