ASP.NET Core中2FA激活后登录验证失败问题排查
问题:ASP.NET Core 2FA验证后再次登录提示无效验证码
我在ASP.NET Core项目中启用2FA功能,已通过扫描二维码在Google Authenticator完成激活,输入6位验证码验证成功后退出应用。但再次登录时,输入Google Authenticator生成的验证码却被提示“无效的验证码”,且手机与PC时间均已设置为自动同步。
相关截图:
- 激活2FA界面:

- 无效验证码提示:

相关代码(EnableAuthenticator.cshtml.cs)
using System; using System.Text; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.Rendering; using produit_chimiques.Areas.Identity.Data; // ← IMPORTANT : ton namespace ApplicationUser namespace produit_chimiques.Areas.Identity.Pages.Account.Manage { [Authorize] public class EnableAuthenticatorModel : PageModel { private readonly UserManager<ApplicationUser> _userManager; private readonly UrlEncoder _urlEncoder; private const string AuthenticatorUriFormat = "otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6"; public EnableAuthenticatorModel( UserManager<ApplicationUser> userManager, UrlEncoder urlEncoder) { _userManager = userManager; _urlEncoder = urlEncoder; } [BindProperty] public InputModel Input { get; set; } = new InputModel(); public string SharedKey { get; set; } = ""; public string AuthenticatorUri { get; set; } = ""; public class InputModel { public string Code { get; set; } = ""; } public async Task<IActionResult> OnGetAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) { return NotFound($"Impossible de charger l'utilisateur avec l'ID '{_userManager.GetUserId(User)}'."); } await LoadSharedKeyAndQrCodeUriAsync(user); return Page(); } public async Task<IActionResult> OnPostAsync() { var user = await _userManager.GetUserAsync(User); if (user == null) { return NotFound($"Impossible de charger l'utilisateur avec l'ID '{_userManager.GetUserId(User)}'."); } if (!ModelState.IsValid) { await LoadSharedKeyAndQrCodeUriAsync(user); return Page(); } // Nettoyer le code saisi var verificationCode = Input.Code.Replace(" ", string.Empty) .Replace("-", string.Empty); ///////////////////////////////////////////////////////////////////////////////////////// // Juste AVANT VerifyTwoFactorTokenAsync, ajoute : var debugKey = await _userManager.GetAuthenticatorKeyAsync(user); Console.WriteLine($"DEBUG: Verification pour user {user.Id}, clé actuelle: {debugKey}"); Console.WriteLine($"DEBUG: Code saisi: {verificationCode}"); // Vérifier le code TOTP avec le provider Identity var is2faTokenValid = await _userManager.VerifyTwoFactorTokenAsync( user, _userManager.Options.Tokens.AuthenticatorTokenProvider, verificationCode); if (!is2faTokenValid) { ModelState.AddModelError("Input.Code", "Code non valide. Vérifiez votre application d'authentification."); await LoadSharedKeyAndQrCodeUriAsync(user); return Page(); } // Activer la 2FA pour cet utilisateur await _userManager.SetTwoFactorEnabledAsync(user, true); await _userManager.ResetAuthenticatorKeyAsync(user); return RedirectToPage("./TwoFactorAuthentication"); } private async Task LoadSharedKeyAndQrCodeUriAsync(ApplicationUser user) { // Générer/récupérer la clé TOTP var unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); if (string.IsNullOrEmpty(unformattedKey)) { await _userManager.ResetAuthenticatorKeyAsync(user); unformattedKey = await _userManager.GetAuthenticatorKeyAsync(user); } SharedKey = FormatKey(unformattedKey); var email = await _userManager.GetEmailAsync(user) ?? await _userManager.GetUserNameAsync(user); AuthenticatorUri = GenerateQrCodeUri(email, unformattedKey); } private string GenerateQrCodeUri(string email, string unformattedKey) { var issuer = "ProduitsChimiques"; return string.Format( AuthenticatorUriFormat, _urlEncoder.Encode(issuer), _urlEncoder.Encode(email), unformattedKey); } private string FormatKey(string unformattedKey) { const int groupSize = 4; var result = new StringBuilder(); var currentPosition = 0; while (currentPosition + groupSize < unformattedKey.Length) { result.Append(unformattedKey.AsSpan(currentPosition, groupSize)); result.Append(' '); currentPosition += groupSize; } if (currentPosition < unformattedKey.Length) { result.Append(unformattedKey.AsSpan(currentPosition)); } return result.ToString().ToLowerInvariant(); } } }
问题原因分析
激活后重置密钥导致不匹配
在OnPostAsync方法中,验证完验证码并启用2FA后,代码调用了await _userManager.ResetAuthenticatorKeyAsync(user);。这个操作会生成全新的TOTP密钥,但Google Authenticator中保存的是激活时扫描二维码获取的旧密钥,后续登录时两边密钥不一致,自然无法通过验证。流程逻辑错误
正常2FA激活流程不需要在验证成功后重置密钥。ResetAuthenticatorKeyAsync应该用于用户丢失设备、需要重新绑定2FA的场景,而非首次激活完成后。
修复方案
修改OnPostAsync方法,移除激活2FA后的密钥重置操作:
// 原代码 await _userManager.SetTwoFactorEnabledAsync(user, true); await _userManager.ResetAuthenticatorKeyAsync(user); // 修改后 await _userManager.SetTwoFactorEnabledAsync(user, true); // 移除 ResetAuthenticatorKeyAsync 调用
修改后,服务器会保留用户激活时使用的密钥,Google Authenticator生成的验证码会与服务器密钥保持一致,后续登录即可正常验证。
内容的提问来源于stack exchange,提问作者Arteta
相关产品推荐
相关产品推荐

