{"record":{"id":"f3c11691955514a9","repo":"ellite/Wallos","slug":"not-a-valid-otp-provisioning-uri-url","errorCode":null,"errorMessage":"Not a valid OTP provisioning URI","messagePattern":"Not a valid OTP provisioning URI","errorType":"validation","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"libs/OTPHP/Url.php","lineNumber":94,"sourceCode":"    {\n        $parsed_url = parse_url($uri);\n        $parsed_url !== false || throw new InvalidArgumentException('Invalid URI.');\n        foreach (['scheme', 'host', 'path', 'query'] as $key) {\n            array_key_exists($key, $parsed_url) || throw new InvalidArgumentException(\n                'Not a valid OTP provisioning URI'\n            );\n        }\n        $scheme = $parsed_url['scheme'] ?? null;\n        $host = $parsed_url['host'] ?? null;\n        $path = $parsed_url['path'] ?? null;\n        $query = $parsed_url['query'] ?? null;\n        $scheme === 'otpauth' || throw new InvalidArgumentException('Not a valid OTP provisioning URI');\n        is_string($host) || throw new InvalidArgumentException('Invalid URI.');\n        is_string($path) || throw new InvalidArgumentException('Invalid URI.');\n        is_string($query) || throw new InvalidArgumentException('Invalid URI.');\n        $parsedQuery = [];\n        parse_str($query, $parsedQuery);\n        array_key_exists('secret', $parsedQuery) || throw new InvalidArgumentException(\n            'Not a valid OTP provisioning URI'\n        );\n        $secret = $parsedQuery['secret'];\n        unset($parsedQuery['secret']);\n\n        return new self($scheme, $host, $path, $secret, $parsedQuery);\n    }\n}\n","sourceCodeStart":76,"sourceCodeEnd":103,"githubUrl":"https://github.com/ellite/Wallos/blob/52820e87ca5a6e105fdbb7f1c0c681bc0cfee2fd/libs/OTPHP/Url.php#L76-L103","documentation":"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.","triggerScenarios":"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').","commonSituations":"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.","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()"],"exampleFix":"// before\n$otp->loadProvisioningUri('otpauth://totp/alice?digits=6');\n// after\n$otp->loadProvisioningUri('otpauth://totp/alice?secret=JBSWY3DPEHPK3PXP&digits=6');","handlingStrategy":"validation","validationCode":"$parts = parse_url($uri);\nparse_str($parts['query'] ?? '', $q);\nif (!isset($q['secret']) || $q['secret'] === '') {\n    throw new \\LogicException('URI must contain a secret query parameter');\n}","typeGuard":null,"tryCatchPattern":"try {\n    $otp->loadProvisioningUri($uri);\n} catch (\\InvalidArgumentException $e) {\n    // prompt user to re-enroll / re-scan QR code\n}","preventionTips":["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"],"tags":["php","otp","uri-parsing"],"backgroundTag":"invalid-url-format","analyzedSha":"52820e87ca5a6e105fdbb7f1c0c681bc0cfee2fd","analyzedAt":"2026-09-13T14:09:30.873Z","contentChangedAt":"2026-09-13T14:09:30.873Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}