TechnitiumSoftware/DnsServer · error · InvalidOperationException

Time-based one-time password (TOTP) was not initialized for

Error message

Time-based one-time password (TOTP) was not initialized for user: 

What it means

Thrown by User.EnableTOTP(totp) when _totpKeyUri is null. EnableTOTP expects the user to have first called InitializedTOTP(issuer), which generates and stores the shared secret; without that secret there is nothing to validate the supplied TOTP code against. The null check precedes authenticator construction so no NullReferenceException leaks.

Source

Thrown at DnsServerCore/Auth/User.cs:300

        {
            if (_isSsoUser)
                throw new InvalidOperationException("Time-based one-time password (TOTP) feature is not available for SSO users.");

            if (_totpEnabled)
                throw new InvalidOperationException("Time-based one-time password (TOTP) is already enabled for user: " + _username);

            _totpKeyUri = AuthenticatorKeyUri.Generate(issuer, _username);

            return _totpKeyUri;
        }

        public void EnableTOTP(string totp)
        {
            if (_isSsoUser)
                throw new InvalidOperationException("Time-based one-time password (TOTP) feature is not available for SSO users.");

            if (_totpKeyUri is null)
                throw new InvalidOperationException("Time-based one-time password (TOTP) was not initialized for user: " + _username);

            if (_totpEnabled)
                throw new InvalidOperationException("Time-based one-time password (TOTP) is already enabled for user: " + _username);

            Authenticator authenticator = new Authenticator(_totpKeyUri);

            if (!authenticator.IsTOTPValid(totp))
                throw new Exception("Invalid time-based one-time password (TOTP) was attempted for user: " + _username);

            _totpEnabled = true;
        }

        public void DisableTOTP()
        {
            if (_isSsoUser)
                throw new InvalidOperationException("Time-based one-time password (TOTP) feature is not available for SSO users.");

            if (!_totpEnabled)

View on GitHub (pinned to d0484b6c1e)

Solutions

  1. Always call InitializedTOTP(issuer) first and only call EnableTOTP with the code from the authenticator app afterward.
  2. Track a server-side 'totp pending initialization' flag so the confirm endpoint rejects requests that skipped initialize.
  3. On the confirm endpoint, if the user has no pending key URI, redirect back to the initialize step.

Example fix

// before
user.EnableTOTP(totpCode);

// after
var keyUri = user.InitializedTOTP(issuer); // store keyUri in session/UI
user.EnableTOTP(totpCode);                  // user enters code from app
Defensive patterns

Strategy: validation

Validate before calling

if (!user.HasPendingTotpKey)
    return BadRequest("Initialize TOTP before confirming a code.");
user.EnableTOTP(totpCode);

Type guard

static bool TotpReadyToEnable(User user) => !user.IsSsoUser && user.HasPendingTotpKey && !user.TotpEnabled;

Prevention

When it happens

Trigger: Calling EnableTOTP before InitializedTOTP on the same user — e.g. the browser refreshes or the user manually POSTs the confirm step without having completed (or after losing) the initialize step.

Common situations: Multi-step wizard state lost between page loads; direct API calls that skip the initialize step; the user opened a second tab and completed setup there, leaving _totpKeyUri null on reload.

Related errors


AI-assisted analysis of TechnitiumSoftware/DnsServer@d0484b6c1e (2026-08-13). Data as JSON: /api/errors/746f15380f8b2e81. Report an issue: GitHub.