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

  1. Fix the provisioning URI so the type segment is exactly 'totp' or 'hotp' (lowercase): otpauth://totp/Label?secret=...
  2. If you only have a secret, construct the OTP directly with TOTP::create($secret) or HOTP::createFromSecret($secret) instead of parsing a URI.
  3. Validate/normalize the URI with a regex like /^otpauth:\/\/(totp|hotp)\// before calling the factory.
  4. 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

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


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)