BeyondDimension/SteamTools · warning · ApplicationException

HOTP authenticator should have a counter

Error message

HOTP authenticator should have a counter

What it means

Thrown when an otpauth://hotp/ entry lacks a 'counter' query parameter. HOTP is counter-based, so the counter is mandatory to know which code to generate next; a HOTP URI without it is incomplete and cannot be imported.

Source

Thrown at src/BD.WTTS.Client.Plugins.Authenticator/UI/ViewModels/AuthenticatorImportPageViewModel.cs:300

                {
                    issuer = label.Substring(0, p);
                    label = label[(p + 1)..];
                }

                // + aren't decoded
                label = label.Replace("+", " ");

                var query = HttpUtility.ParseQueryString(uri.Query);
                string? secret = query["secret"];
                if (string.IsNullOrEmpty(secret))
                {
                    throw new ApplicationException("Authenticator does not contain secret");
                }

                string? counter = query["counter"];
                if (uri.Host == "hotp" && string.IsNullOrEmpty(counter))
                {
                    throw new ApplicationException("HOTP authenticator should have a counter");
                }

                AuthenticatorDTO authenticatorDto = new();

                AuthenticatorValueDTO auth;
                if (string.Compare(issuer, "BattleNet", StringComparison.OrdinalIgnoreCase) == 0)
                {
                    string? serial = query["serial"];
                    if (string.IsNullOrEmpty(serial))
                    {
                        throw new ApplicationException("Battle.net Authenticator does not have a serial");
                    }

                    serial = serial.ToUpper();
                    if (Regex.IsMatch(serial, @"^[A-Z]{2}-?[\d]{4}-?[\d]{4}-?[\d]{4}$") == false)
                    {
                        throw new ApplicationException("Invalid serial for Battle.net Authenticator");
                    }

View on GitHub (pinned to c16ffa08e0)

Solutions

  1. Re-export the HOTP entry with its current counter value included.
  2. Manually append &counter=0 (or the known current counter) to the URI before importing.
  3. If the true counter is unknown, default to 0 and let the user resync after import.
  4. Validate counter presence in the UI for HOTP entries before parsing.

Example fix

// before
if (uri.Host == "hotp" && string.IsNullOrEmpty(counter))
    throw new ApplicationException("HOTP authenticator should have a counter");

// after: default to 0 with a warning when counter is missing
if (uri.Host == "hotp")
    counter = string.IsNullOrEmpty(counter) ? "0" : counter;
Defensive patterns

Strategy: validation

Validate before calling

if (uri.Host.Equals("hotp", StringComparison.OrdinalIgnoreCase))
{
    var counter = query["counter"];
    if (string.IsNullOrEmpty(counter) || !long.TryParse(counter, out _))
        errors.Add(($"Line {n}: HOTP entry missing a numeric 'counter'.", line));
}

Type guard

bool HotpHasCounter(Uri u) => !(u.Host == "hotp") || !string.IsNullOrEmpty(HttpUtility.ParseQueryString(u.Query)["counter"]);

Try / catch

try { ParseAuthenticator(line); }
catch (ApplicationException ex) when (ex.Message == "HOTP authenticator should have a counter")
{
    // Optionally default counter to 0 with a warning and retry parse.
    line = line + (line.Contains("?") ? "&" : "?") + "counter=0";
    ParseAuthenticator(line);
}

Prevention

When it happens

Trigger: Importing an otpauth://hotp/ URI whose query string has no 'counter' key or an empty value.

Common situations: Export tool omitted the counter; URI truncated; entry was converted from TOTP incorrectly; counter was set to a non-numeric/empty value.

Understand the failure class

Related errors


AI-assisted analysis of BeyondDimension/SteamTools@c16ffa08e0 (2026-08-13). Data as JSON: /api/errors/29650f769bb0a743. Report an issue: GitHub.