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
- Re-export the HOTP entry with its current counter value included.
- Manually append &counter=0 (or the known current counter) to the URI before importing.
- If the true counter is unknown, default to 0 and let the user resync after import.
- 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
- For HOTP entries, require and validate a numeric counter before import.
- Default missing counters to 0 and let the user resync after import.
- Export tools should always include the current counter for HOTP.
- Surface a HOTP-specific hint in the UI ('this entry needs a counter').
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
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Import only supports otpauth://totp/ or otpauth://hotp/
- Import only supports otpauth://
- Authenticator does not contain secret
- Battle.net Authenticator does not have a serial
- Invalid serial for Battle.net Authenticator
AI-assisted analysis of BeyondDimension/SteamTools@c16ffa08e0 (2026-08-13).
Data as JSON: /api/errors/29650f769bb0a743.
Report an issue: GitHub.