{"record":{"id":"cf3ad97c38168bba","repo":"fullstackhero/dotnet-starter-kit","slug":"failed-to-generate-authenticator-key","errorCode":null,"errorMessage":"Failed to generate authenticator key.","messagePattern":"Failed to generate authenticator key\\.","errorType":"exception","errorClass":"CustomException","httpStatus":400,"severity":"error","filePath":"src/Modules/Identity/Modules.Identity/Features/v1/TwoFactor/Enroll/EnrollTwoFactorCommandHandler.cs","lineNumber":44,"sourceCode":"    public async ValueTask<TwoFactorEnrollmentResponse> Handle(\n        EnrollTwoFactorCommand command, CancellationToken cancellationToken)\n    {\n        ArgumentNullException.ThrowIfNull(command);\n\n        if (!_currentUser.IsAuthenticated())\n        {\n            throw new UnauthorizedException();\n        }\n\n        var userId = _currentUser.GetUserId().ToString();\n        var user = await _userManager.FindByIdAsync(userId)\n            ?? throw new NotFoundException($\"User {userId} not found.\");\n\n        // Always reset so calling enroll twice rotates the secret — prevents stale codes\n        // from a prior incomplete enrollment from silently succeeding.\n        await _userManager.ResetAuthenticatorKeyAsync(user);\n        var sharedKey = await _userManager.GetAuthenticatorKeyAsync(user)\n            ?? throw new CustomException(\"Failed to generate authenticator key.\");\n\n        var email = user.Email ?? user.UserName ?? user.Id;\n        var authenticatorUri = string.Format(\n            System.Globalization.CultureInfo.InvariantCulture,\n            \"otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6\",\n            UrlEncoder.Default.Encode(IssuerName),\n            UrlEncoder.Default.Encode(email),\n            sharedKey);\n\n        return new TwoFactorEnrollmentResponse(sharedKey, authenticatorUri);\n    }\n}\n","sourceCodeStart":26,"sourceCodeEnd":57,"githubUrl":"https://github.com/fullstackhero/dotnet-starter-kit/blob/3f2959e683e9f83f13e55e1678c9119f63c7e8e5/src/Modules/Identity/Modules.Identity/Features/v1/TwoFactor/Enroll/EnrollTwoFactorCommandHandler.cs#L26-L57","documentation":"EnrollTwoFactorCommandHandler throws CustomException('Failed to generate authenticator key.') when GetAuthenticatorKeyAsync returns null right after ResetAuthenticatorKeyAsync. Under normal ASP.NET Identity operation this never happens; it signals the authenticator token provider is not configured or key persistence failed.","triggerScenarios":"The AuthenticatorTokenProvider is missing from IdentityOptions.Tokens.ProviderMap, so ResetAuthenticatorKeyAsync silently does nothing and GetAuthenticatorKeyAsync returns null; the user's security-stamp/token storage is broken; a custom IUserTokenProvider is misconfigured.","commonSituations":"Identity options configured without MapTokenProvider for 'Authenticator'; a partially customized Identity setup after upgrading packages; a custom token provider that fails to write the key.","solutions":["Ensure identityOptions.Tokens.AuthenticatorTokenProvider is set and the provider is mapped: options.Tokens.ProviderMap[TokenOptions.DefaultAuthenticatorProvider] = new TokenProviderDescriptor(typeof(AuthenticatorTokenProvider<User>))","Verify ResetAuthenticatorKeyAsync succeeded (check result.Succeeded) and the user store supports authentication token storage","Write a test that resets and reads the key for a seeded user to catch provider misconfiguration early","Check logs around the reset call for silent failures from the token provider"],"exampleFix":"// before\nservices.AddIdentity<User, Role>().AddDefaultTokenProviders();\n// after\nservices.AddIdentity<User, Role>().AddDefaultTokenProviders();\nservices.PostConfigure<IdentityOptions>(o =>\n    o.Tokens.ProviderMap[TokenOptions.DefaultAuthenticatorProvider] =\n        new TokenProviderDescriptor<AuthenticatorTokenProvider<User>>());","handlingStrategy":"try-catch","validationCode":"// server-side smoke test at startup or in CI\nvar testUser = await SeedUserAsync();\nawait userManager.ResetAuthenticatorKeyAsync(testUser);\nif (await userManager.GetAuthenticatorKeyAsync(testUser) is null)\n    throw new InvalidOperationException(\"Authenticator token provider not configured\");","typeGuard":null,"tryCatchPattern":"try {\n  const qr = await api.enrollTwoFactor();\n} catch (e) {\n  if (e.status === 500 && /authenticator key/i.test(e.message)) {\n    reportConfigBug(); // provider map missing — server-side issue\n  }\n  throw e;\n}","preventionTips":["Always call AddDefaultTokenProviders (or map the Authenticator provider) in Identity setup","Assert key generation in an integration test for the enroll flow","Keep the Identity provider configuration in one reviewed place"],"tags":["two-factor","identity","configuration","aspnet-identity"],"backgroundTag":"invalid-config-value","analyzedSha":"3f2959e683e9f83f13e55e1678c9119f63c7e8e5","analyzedAt":"2026-09-15T22:20:53.684Z","contentChangedAt":"2026-09-15T22:20:53.684Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}