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

  1. Ensure the URI has a type segment: otpauth://totp/LABEL?secret=...
  2. Validate the URI shape with a regex or parse_url in your code before calling loadProvisioningUri
  3. 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

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


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)