ellite/Wallos · error · InvalidArgumentException
Not a valid OTP provisioning URI
Error message
Not a valid OTP provisioning URI
What it means
After parsing the otpauth URI's query string, Url::fromString() requires a 'secret' key. Line 94 throws 'Not a valid OTP provisioning URI' when the query has no secret parameter, since a provisioning URI without a shared secret cannot produce a working OTP.
Solutions
- Append the secret to the query string: ?secret=BASE32SECRET
- Verify the parameter name is exactly 'secret' (lowercase)
- If you lack a secret, create the OTP from a generated one via TOTP::generateSecret()
Example fix
// before
$otp->loadProvisioningUri('otpauth://totp/alice?digits=6');
// after
$otp->loadProvisioningUri('otpauth://totp/alice?secret=JBSWY3DPEHPK3PXP&digits=6'); Defensive patterns
Strategy: validation
Validate before calling
$parts = parse_url($uri);
parse_str($parts['query'] ?? '', $q);
if (!isset($q['secret']) || $q['secret'] === '') {
throw new \LogicException('URI must contain a secret query parameter');
} Try / catch
try {
$otp->loadProvisioningUri($uri);
} catch (\InvalidArgumentException $e) {
// prompt user to re-enroll / re-scan QR code
} Prevention
- Confirm the query key is exactly 'secret'
- Never redact the secret from URIs you intend to load
- Validate provisioning URIs server-side before storing/processing them
When it happens
Trigger: Calling loadProvisioningUri() with 'otpauth://totp/alice?digits=6&period=30' — scheme/host/path are fine, but parse_str yields no 'secret' key, or the secret query key is misspelled (e.g. 'secrets').
Common situations: Sharing URIs where the secret was stripped for security; hand-writing URIs and forgetting secret=; URI builders that URL-encode the secret under a different parameter name.
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
- Invalid 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/f3c11691955514a9.
Report an issue: GitHub.
Appendix: source
Thrown at libs/OTPHP/Url.php:94
{
$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)