ellite/Wallos · error · InvalidArgumentException
Invalid URI.
Error message
Invalid URI.
What it means
Url::fromString() parses an otpauth:// provisioning URI with parse_url() and validates each component's type. Line 89 throws 'Invalid URI.' when the parsed array has no usable string 'host' — i.e. the URI's issuer/type part after otpauth:// is missing or not parseable as a host.
Solutions
- Ensure the URI has a type segment: otpauth://totp/LABEL?secret=...
- Validate the URI shape with a regex or parse_url in your code before calling loadProvisioningUri
- Regenerate the URI with $totp->getProvisioningUri() instead of hand-assembling it
Example fix
// before
$otp->loadProvisioningUri('otpauth://?secret=JBSWY3DPEHPK3PXP');
// after
$otp->loadProvisioningUri('otpauth://totp/alice?secret=JBSWY3DPEHPK3PXP'); Defensive patterns
Strategy: validation
Validate before calling
$parts = parse_url($uri);
if (($parts['scheme'] ?? null) !== 'otpauth'
|| !is_string($parts['host'] ?? null)
|| !in_array($parts['host'], ['totp', 'hotp'], true)) {
throw new \LogicException('URI must be otpauth://totp/... or otpauth://hotp/...');
} Try / catch
try {
$otp->loadProvisioningUri($uri);
} catch (\InvalidArgumentException $e) {
// fall back to manual construction from extracted secret
$otp = TOTP::createFromSecret($secret);
} Prevention
- Only accept otpauth URIs that include the type segment (totp/hotp)
- Regenerate URIs via getProvisioningUri() instead of hand-building them
- Check for unfilled template placeholders before loading
When it happens
Trigger: Passing a URI like 'otpauth://?secret=...' or 'otpauth:///path?secret=...' to $otp->loadProvisioningUri() where parse_url yields no string host component.
Common situations: Hand-built or truncated otpauth URIs pasted from emails/logs; URIs missing the totp/hotp type segment (otpauth://totp/...); template placeholders left unfilled (otpauth://%type%/%label%?...).
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- Not a valid OTP provisioning URI
- Unsupported " " OTP type
- Invalid "counter" parameter.
- 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/629eac066ab0f80a.
Report an issue: GitHub.
Appendix: source
Thrown at libs/OTPHP/Url.php:90
/**
* @param non-empty-string $uri
*/
public static function fromString(string $uri): self
{
$parsed_url = parse_url($uri);
$parsed_url !== false || throw new InvalidArgumentException('Invalid URI.');
foreach (['scheme', 'host', 'path', 'query'] as $key) {
array_key_exists($key, $parsed_url) || throw new InvalidArgumentException(
'Not a valid OTP provisioning URI'
);
}
$scheme = $parsed_url['scheme'] ?? null;
$host = $parsed_url['host'] ?? null;
$path = $parsed_url['path'] ?? null;
$query = $parsed_url['query'] ?? null;
$scheme === 'otpauth' || throw new InvalidArgumentException('Not a valid OTP provisioning URI');
is_string($host) || throw new InvalidArgumentException('Invalid URI.');
is_string($path) || throw new InvalidArgumentException('Invalid URI.');
is_string($query) || throw new InvalidArgumentException('Invalid URI.');
$parsedQuery = [];
parse_str($query, $parsedQuery);
array_key_exists('secret', $parsedQuery) || throw new InvalidArgumentException(
'Not a valid OTP provisioning URI'
);
$secret = $parsedQuery['secret'];
unset($parsedQuery['secret']);
return new self($scheme, $host, $path, $secret, $parsedQuery);
}
}
View on GitHub (pinned to 52820e87ca)