ellite/Wallos · error · InvalidArgumentException
Unsupported " " OTP type
Error message
Unsupported "%s" OTP type
What it means
Factory::createOTP only supports the 'totp' and 'hotp' otpauth URI types (the URL host). When a provisioning URI's host is anything else, an InvalidArgumentException is thrown. This guards against malformed or non-standard otpauth:// URIs.
Solutions
- Fix the provisioning URI so the type segment is exactly 'totp' or 'hotp' (lowercase): otpauth://totp/Label?secret=...
- If you only have a secret, construct the OTP directly with TOTP::create($secret) or HOTP::createFromSecret($secret) instead of parsing a URI.
- Validate/normalize the URI with a regex like /^otpauth:\/\/(totp|hotp)\// before calling the factory.
- Check the URI was not URL-decoded/mangled upstream (e.g. scheme stripped so the host becomes something unexpected).
Example fix
// before
$otp = $factory->loadFromProvisioningUri('otpauth://TOTP/alice?secret=JBSW...');
// after
$otp = $factory->loadFromProvisioningUri('otpauth://totp/alice?secret=JBSW...'); Defensive patterns
Strategy: validation
Validate before calling
if (!preg_match('#^otpauth://(totp|hotp)/#', $uri)) {
throw new InvalidArgumentException('Provisioning URI must be otpauth://totp/ or otpauth://hotp/');
} Type guard
function isSupportedOtpType(string $uri): bool {
$host = parse_url($uri, PHP_URL_HOST);
return $host === 'totp' || $host === 'hotp';
} Try / catch
try {
$otp = $factory->loadFromProvisioningUri($uri);
} catch (InvalidArgumentException $e) {
log_error('Unsupported OTP type in URI', ['uri' => $uri]);
throw new InvalidProvisioningUriException($uri, $e);
} Prevention
- Only generate URIs via the library's own getProvisioningUri()
- Regex-validate otpauth URIs from external sources before parsing
- Lowercase the type segment before passing to the factory
- Never hand-build otpauth:// URIs with string concatenation
When it happens
Trigger: loadFromProvisioningUri() is given a URI whose host part (the OTP type segment, e.g. otpauth://foo/...) is not exactly 'totp' or 'hotp' — e.g. typos like 'TOTP' (case handled?) or 'otp', or a URI missing the type segment entirely.
Common situations: Hand-edited provisioning URIs, URIs generated by third-party authenticator tools using non-standard type names, copying URIs from QR decoders that mangle the scheme/host, or passing a plain secret instead of a full otpauth URI.
Understand the failure class
Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.
Related errors
- The label is not set.
- Label must not contain a colon.
- Issuer must not contain a colon.
- Invalid "counter" parameter.
- The counter must be at least 0.
AI-assisted analysis of ellite/Wallos@52820e87ca (2026-09-13).
Data as JSON: /api/errors/958b0db1f738c758.
Report an issue: GitHub.
Appendix: source
Thrown at libs/OTPHP/Factory.php:88
$otp->setIssuer($result[0]);
}
private static function createOTP(Url $parsed_url, ClockInterface $clock): OTPInterface
{
switch ($parsed_url->getHost()) {
case 'totp':
$totp = TOTP::createFromSecret($parsed_url->getSecret(), $clock);
$totp->setLabel(self::getLabel($parsed_url->getPath()));
return $totp;
case 'hotp':
$hotp = HOTP::createFromSecret($parsed_url->getSecret());
$hotp->setLabel(self::getLabel($parsed_url->getPath()));
return $hotp;
default:
throw new InvalidArgumentException(sprintf('Unsupported "%s" OTP type', $parsed_url->getHost()));
}
}
/**
* @param non-empty-string $data
* @return non-empty-string
*/
private static function getLabel(string $data): string
{
$result = explode(':', rawurldecode(mb_substr($data, 1)));
$label = count($result) === 2 ? $result[1] : $result[0];
assert($label !== '');
return $label;
}
}
View on GitHub (pinned to 52820e87ca)