无效的 2FA 代码(身份验证器应用程序和电子邮件)

问题描述 投票:0回答:1

我面临一个问题,即身份验证器应用程序和电子邮件验证码都被标记为“无效”,即使它们是正确的。

在添加电子邮件身份验证之前,通过身份验证器应用程序进行的 2FA 工作正常,但在添加电子邮件版本之后,它们都损坏了。即使输入了正确的代码,身份验证器和电子邮件代码都被标记为“无效”。

我尝试过的: 检查电子邮件和身份验证器应用程序的代码生成(令牌已正确生成并发送)。 在验证之前,请仔细检查输入代码是否已删除空格和破折号。

有任何调试技巧或需要研究的领域吗? 预先感谢您的帮助!

我的代码的相关部分是:

AccountController.cs:

   [HttpPost]
   [AllowAnonymous]
   [ValidateAntiForgeryToken]
   public async Task<IActionResult> Login(LoginViewModel model, string? returnUrl = null)
   {
       ViewData["ReturnUrl"] = returnUrl;

       model.Username = model.Username?[..Math.Min(model.Username.Length, 256)] ?? string.Empty;

       var user = await _userManager.Users.FirstOrDefaultAsync(t => t.UserName == model.Username && t.AccessEnabled);

       // Password attempt: lockout if failure threshold reached
       var result = await _signInManager.PasswordSignInAsync(user, model.Password, model.RememberMe, lockoutOnFailure: true);

       if (result.Succeeded)
       {
           await LogSuccessfulLogin(user);
           // Check if user needs to enable 2FA
           if (user.Is2FArequired)
           {
               return RedirectToAction(nameof(TwoStepVerification), new { userId = user.Id, rememberMe = model.RememberMe, returnUrl });
           }
           
           return RedirectToLocal(returnUrl);
       }
       if (result.RequiresTwoFactor)
       {
           return RedirectToAction(nameof(TwoStepVerification), new { userId = user.Id, rememberMe = model.RememberMe, returnUrl });
       }
       if (result.IsLockedOut)
       {
           await LogAccountLocked(user);
           return RedirectToAction(nameof(Lockout));
       }

       // Log failed attempt with generic message
       await LogFailedLoginAttempt(user.UserName);
       ModelState.AddModelError("CustomError", "Login failed. Please try again.");
       return View(model);
   }

   // GET: Two-step verification method selection
   [HttpGet]
   [AllowAnonymous]
   public async Task<IActionResult> TwoStepVerification(string userId, bool rememberMe, string? returnUrl = null)
   {
       var user = await _userManager.Users.FirstOrDefaultAsync(t => t.Id.ToLower() == userId.ToLower() && t.AccessEnabled);
       if (user == null)
       {
           return RedirectToAction("Login");
       }

       var model = new TwoStepVerificationViewModel
       {
           Email = user.Email, // Assume user has an email
           TwoFactorMethod = string.Empty,
           UserId = user.Id,
           RememberMe = rememberMe,
       };

       ViewData["ReturnUrl"] = returnUrl;
       return View(model); // This view will allow the user to choose the 2FA method (email or authenticator)
   }

   [HttpPost]
   [AllowAnonymous]
   [ValidateAntiForgeryToken]
   public async Task<IActionResult> TwoStepVerification(TwoStepVerificationViewModel model, string? returnUrl = null)
   {
       var user = await _userManager.Users.FirstOrDefaultAsync(t => t.Id.ToLower() == model.UserId.ToLower() && t.AccessEnabled);

       // Check the selected method
       if (model.TwoFactorMethod == "email")
       {
           return RedirectToAction(nameof(LoginWith2faEmail), new { userId = model.UserId, rememberMe = model.RememberMe, returnUrl });
       }
       else if (model.TwoFactorMethod == "authenticator")
       {          
           return RedirectToAction(nameof(LoginWith2fa), new { userId = model.UserId, returnUrl });
       }
       return View(model);
   }

   [HttpGet]
   [AllowAnonymous]
   public async Task<IActionResult> LoginWith2faEmail(string userId, bool rememberMe, string? returnUrl = null)
   {
       ViewData["ReturnUrl"] = returnUrl;
       
       var user = await _userManager.Users.FirstOrDefaultAsync(t => t.Id.ToLower() == userId.ToLower() && t.AccessEnabled);
      
       LoginWith2faViewModel model = new()
       {
           RememberMe = rememberMe,
           UserId = user.Id,
       };

       // Generate the token for email
       var token = await _userManager.GenerateTwoFactorTokenAsync(user, "Email");
       // Prepare the email body
       
       return View(model);
   }

   [HttpPost]
   [AllowAnonymous]
   public async Task<IActionResult> LoginWith2faEmail(LoginWith2faViewModel model, string? returnUrl = null)
   {
       // Remove spaces and dashes from the code input
       var verificationCode = model.TwoFactorCode?.Replace(" ", string.Empty).Replace("-", string.Empty);

       var result = await _signInManager.TwoFactorSignInAsync("Email", verificationCode, model.RememberMe, model.RememberMachine);

       if (result.Succeeded)
       {
           _logger.LogInformation("User {UserId} successfully logged in with 2FA.", model.UserId);
            var user = await _userManager.Users.FirstOrDefaultAsync(t => t.Id.ToLower() == model.UserId.ToLower() && t.AccessEnabled);
           return RedirectToLocal(returnUrl);
       }
       if (result.IsLockedOut)
       {
           var user = await _userManager.GetUserAsync(User);
           _logger.LogWarning("User with ID {UserId} account locked after 2FA attempt.", model.UserId);
           return RedirectToAction(nameof(Lockout));
       }

       _logger.LogWarning("Invalid authenticator code entered");
       return View(model);
   }

   [HttpGet]
   [AllowAnonymous]
   public async Task<IActionResult> LoginWith2fa(string userId, bool rememberMe = false, string? returnUrl = null)
   {
       // Ensure the user ID is valid and access is enabled
       var user = await _userManager.Users.FirstOrDefaultAsync(t => t.Id.ToLower() == userId.ToLower() && t.AccessEnabled);

       var model = new LoginWith2faViewModel
       {
           RememberMe = rememberMe,
           UserId = user.Id
       };

       ViewData["ReturnUrl"] = returnUrl;
       return View(model);
   }

   [HttpPost]
   [AllowAnonymous]
   public async Task<IActionResult> LoginWith2fa(LoginWith2faViewModel model, string? returnUrl = null)
   {
       try
       {
           // Load the user by ID and ensure access is enabled
           var user = await _userManager.Users.FirstOrDefaultAsync(t => t.Id.ToLower() == model.UserId.ToLower() && t.AccessEnabled);

           // Remove spaces and dashes from the code input
           var verificationCode = model.TwoFactorCode?.Replace(" ", string.Empty).Replace("-", string.Empty);

           var result = await _signInManager.TwoFactorAuthenticatorSignInAsync(verificationCode, model.RememberMe, model.RememberMachine);

           if (result.Succeeded)
           {
               _logger.LogInformation("User {UserId} successfully logged in with 2FA.", model.UserId);
               return RedirectToLocal(returnUrl);
           }
           else if (result.IsLockedOut)
           {
               _logger.LogWarning("User with ID {UserId} account locked after 2FA attempt.", user.Id);
               return RedirectToAction(nameof(Lockout));
           }
           else
           {
               _logger.LogWarning("Invalid authenticator code entered for user '{UserId}'.", user.Id);
               ModelState.AddModelError(string.Empty, "Invalid code. Please try again.");
               return View(model);
           }
       }
       catch (Exception ex)
       {
           _logger.LogError(ex, "Exception occurred during 2FA login attempt for user {UserId}.", model.UserId);
           ModelState.AddModelError(string.Empty, "An error occurred during the login process. Please try again.");
       }
       ModelState.AddModelError(string.Empty, "There was a problem logging in. Please try again.");
       return View(model);
   }

程序.cs

builder.Services.AddDbContext<IdentityContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"),
    sqlServerOptionsAction: sqlOptions =>
    {
        sqlOptions.EnableRetryOnFailure(
            maxRetryCount: 5,
            maxRetryDelay: TimeSpan.FromSeconds(30),
            errorNumbersToAdd: null);
    })
);
builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<IdentityContext>()
    .AddDefaultTokenProviders();

#region Identity Configuration
builder.Services.Configure<IdentityOptions>(options =>
{
    options.Password.RequireDigit = true;
    options.Password.RequiredLength = 8;
    options.Password.RequireNonAlphanumeric = false;
    options.Password.RequireUppercase = true;
    options.Password.RequireLowercase = true;
    options.Password.RequiredUniqueChars = 6;

    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);
    options.Lockout.MaxFailedAccessAttempts = 5;
    options.Lockout.AllowedForNewUsers = true;

    options.User.RequireUniqueEmail = true;
    options.Tokens.AuthenticatorTokenProvider = TokenOptions.DefaultAuthenticatorProvider;
});
.net model-view-controller asp.net-identity .net-6.0
1个回答
0
投票

检查您的代码后,我看不出有任何问题,但是,问题可能出在

_signInManager
的内部工作中。

我想到了一些潜在的问题。

1:身份 cookie 不存在

下面的两种方法都需要来自

TwoFactorUserIdScheme
的 cookie 出现在请求标头中。

  • _signInManager.TwoFactorSignInAsync()
  • _signInManager.TwoFactorAuthenticatorSignInAsync()

您已在

Program.cs
中正确配置了身份,因此自您调用
_signInManager.PasswordSignInAsync()
方法以来,应在标头中传递此 cookie。根据
ApplicationScheme
设置
TwoFactorUserIdScheme
SignInResult
cookie。

我假设

_signInManager
无法从 cookie 检索 2FA 数据,无法找到要进行身份验证的用户,因此返回失败的
SignInResult

2:使用 ViewModel 代替 cookie

如上述案例所述,

_signInManager
需要 cookie 来检索 2FA 数据并对用户进行身份验证。

如果您想使用 ViewModel 中传递的数据来检索 2FA 数据并对用户进行身份验证,您必须实现自己的登录逻辑。例如:

[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> LoginWith2faEmail(LoginWith2faViewModel model, string? returnUrl = null)
{
    // Remove spaces and dashes from the code input
    var verificationCode = model.TwoFactorCode?.Replace(" ", string.Empty).Replace("-", string.Empty);

    // Find the user by Id.
    var user = await _userManager.FindByIdAsync(model.UserId);
    if (user == null)
    {
        // Do something when user isn't found.
    }

    // Check if user is allowed to sign in.
    if (!await _signInmanager.CanSignInAsync(user) && await _userManager.IsLockedOutAsync(user))
    {
        // Do something when user isn't allowed to sign in.
    }

    if (!await _userManager.VerifyTwoFactorTokenAsync(user, "Email", verificationCode))
    {
        // Do something when the user inputs the wrong code.
    }

    // Finally when the user is allowed to sign in, sign them in.
    await _signInmanager.SignInAsync(user, model.RememberMe, "Your schema name");

    return View(model);
}

希望这可以帮助解决您的问题!

© www.soinside.com 2019 - 2024. All rights reserved.