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));
        }