You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core中2FA激活后登录验证失败问题排查

问题:ASP.NET Core 2FA验证后再次登录提示无效验证码

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

相关截图:

  • 激活2FA界面:激活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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 12:14:52