fullstackhero/dotnet-starter-kit · error · CustomException
Failed to generate authenticator key.
Error message
Failed to generate authenticator key.
What it means
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.
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
Example fix
// before
services.AddIdentity<User, Role>().AddDefaultTokenProviders();
// after
services.AddIdentity<User, Role>().AddDefaultTokenProviders();
services.PostConfigure<IdentityOptions>(o =>
o.Tokens.ProviderMap[TokenOptions.DefaultAuthenticatorProvider] =
new TokenProviderDescriptor<AuthenticatorTokenProvider<User>>()); Defensive patterns
Strategy: try-catch
Validate before calling
// server-side smoke test at startup or in CI
var testUser = await SeedUserAsync();
await userManager.ResetAuthenticatorKeyAsync(testUser);
if (await userManager.GetAuthenticatorKeyAsync(testUser) is null)
throw new InvalidOperationException("Authenticator token provider not configured"); Try / catch
try {
const qr = await api.enrollTwoFactor();
} catch (e) {
if (e.status === 500 && /authenticator key/i.test(e.message)) {
reportConfigBug(); // provider map missing — server-side issue
}
throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/cf3ad97c38168bba.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Identity/Modules.Identity/Features/v1/TwoFactor/Enroll/EnrollTwoFactorCommandHandler.cs:44
public async ValueTask<TwoFactorEnrollmentResponse> Handle(
EnrollTwoFactorCommand command, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(command);
if (!_currentUser.IsAuthenticated())
{
throw new UnauthorizedException();
}
var userId = _currentUser.GetUserId().ToString();
var user = await _userManager.FindByIdAsync(userId)
?? throw new NotFoundException($"User {userId} not found.");
// Always reset so calling enroll twice rotates the secret — prevents stale codes
// from a prior incomplete enrollment from silently succeeding.
await _userManager.ResetAuthenticatorKeyAsync(user);
var sharedKey = await _userManager.GetAuthenticatorKeyAsync(user)
?? throw new CustomException("Failed to generate authenticator key.");
var email = user.Email ?? user.UserName ?? user.Id;
var authenticatorUri = string.Format(
System.Globalization.CultureInfo.InvariantCulture,
"otpauth://totp/{0}:{1}?secret={2}&issuer={0}&digits=6",
UrlEncoder.Default.Encode(IssuerName),
UrlEncoder.Default.Encode(email),
sharedKey);
return new TwoFactorEnrollmentResponse(sharedKey, authenticatorUri);
}
}
View on GitHub (pinned to 3f2959e683)