ellite/Wallos · error · InvalidArgumentException
Label must not contain a colon.
Error message
Label must not contain a colon.
What it means
OTP::generateURI rejects labels containing a colon via hasColon(), throwing InvalidArgumentException. In the otpauth scheme the colon separates issuer from account in labels, and this library wants the issuer supplied separately (setIssuer) rather than embedded in the label.
Solutions
- Remove the colon from the label: use setLabel('alice@example.com').
- Set the issuer separately: $otp->setIssuer('My Company') — the library renders 'Issuer:account' correctly itself.
- Strip or replace colons: str_replace(':', ' ', $label) before setLabel().
- Validate labels with strpos($label, ':') === false before setting.
Example fix
// before
$otp->setLabel('Acme: alice@example.com');
// after
$otp->setIssuer('Acme');
$otp->setLabel('alice@example.com'); Defensive patterns
Strategy: validation
Validate before calling
if (str_contains($label, ':')) {
[$issuer, $account] = explode(':', $label, 2);
$otp->setIssuer(trim($issuer));
$label = trim($account);
}
$otp->setLabel($label); Type guard
function isCleanLabel(string $label): bool {
return $label !== '' && !str_contains($label, ':');
} Try / catch
try {
$uri = $otp->getProvisioningUri();
} catch (InvalidArgumentException $e) {
log_error('Bad OTP label', ['label' => $otp->getLabel()]);
throw new InvalidOtpConfigurationException($e);
} Prevention
- Never embed 'Issuer:account' in the label; use setIssuer() instead
- Sanitize user-chosen display names before using them as labels
- Normalize labels on import from other authenticator ecosystems
- Validate label format in unit tests for enrollment flows
When it happens
Trigger: setLabel('My Company: alice@example.com') followed by getProvisioningUri(); labels copied from other tools that embed 'Issuer:account' in one string.
Common situations: Migrating from libraries that expect 'Issuer:user' labels; concatenating issuer and username manually; copy-pasting full labels from decoded QR payloads.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- Invalid "counter" parameter.
- Invalid "digits" parameter.
- Unsupported " " OTP type
- The counter must be at least 0.
- Counter must be at least 0.
AI-assisted analysis of ellite/Wallos@52820e87ca (2026-09-13).
Data as JSON: /api/errors/6a1689cea3932350.
Report an issue: GitHub.
Appendix: source
Thrown at libs/OTPHP/OTP.php:103
if (isset($options[$key]) && $default === $options[$key]) {
unset($options[$key]);
}
}
ksort($options);
}
/**
* @param non-empty-string $type
* @param array<non-empty-string, mixed> $options
*
* @return non-empty-string
*/
protected function generateURI(string $type, array $options): string
{
$label = $this->getLabel();
is_string($label) || throw new InvalidArgumentException('The label is not set.');
$this->hasColon($label) === false || throw new InvalidArgumentException('Label must not contain a colon.');
$options = [...$options, ...$this->getParameters()];
$this->filterOptions($options);
$params = str_replace(['+', '%7E'], ['%20', '~'], http_build_query($options, '', '&'));
return sprintf(
'otpauth://%s/%s?%s',
$type,
rawurlencode(($this->getIssuer() !== null ? $this->getIssuer() . ':' : '') . $label),
$params
);
}
/**
* @param non-empty-string $safe
* @param non-empty-string $user
*/
protected function compareOTP(string $safe, string $user): bool
{View on GitHub (pinned to 52820e87ca)