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)));
        }
Exemple #2
0
        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());
        }
Exemple #3
0
        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)
                }));
            }
        }
Exemple #4
0
        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));
        }
Exemple #5
0
    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)
            }));
        }
    }