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

  1. Ensure identityOptions.Tokens.AuthenticatorTokenProvider is set and the provider is mapped: options.Tokens.ProviderMap[TokenOptions.DefaultAuthenticatorProvider] = new TokenProviderDescriptor(typeof(AuthenticatorTokenProvider<User>))
  2. Verify ResetAuthenticatorKeyAsync succeeded (check result.Succeeded) and the user store supports authentication token storage
  3. Write a test that resets and reads the key for a seeded user to catch provider misconfiguration early
  4. 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

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.

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)