passbolt/passbolt_api · error · Exception
The version is invalid.
Error message
The version is invalid.
What it means
assertVersion ensures the challenge's version field equals the PROTOCOL_VERSION constant supported by this authenticator. A mismatch throws a plain Exception which verifyChallenge wraps into 'The challenge is invalid. Validation Failed.' — the protocol version negotiated by the client is not supported.
Solutions
- Align the client SDK version with the server's passbolt version and the PROTOCOL_VERSION it expects
- Set the challenge's version field exactly to the protocol constant (case-sensitive string)
- Check passbolt release notes for JWT protocol version changes after upgrading the server
- Inspect server logs for the logged challenge to see which version value was sent
Example fix
// before
const challenge = { version: 'v1', ... };
// after (server expects PROTOCOL_VERSION 'v2')
const challenge = { version: 'v2', ... }; Defensive patterns
Strategy: fallback
Validate before calling
if (challenge.version !== SUPPORTED_PROTOCOL_VERSION) challenge.version = SUPPORTED_PROTOCOL_VERSION; // or rebuild challenge
Type guard
function hasSupportedVersion(c) { return typeof c.version === 'string' && c.version === 'v2'; } Try / catch
try { await login(challenge); } catch (e) { if (/Validation Failed/.test(e.message) && challenge.version !== 'v2') { await login({ ...challenge, version: 'v2' }); } } Prevention
- Read the server's advertised protocol version from /auth/verify.json when available
- Keep client SDK and server on compatible releases
- Use the exact lowercase version string; comparisons are strict
- Add a contract test asserting the challenge version after upgrades
When it happens
Trigger: verifyChallenge on POST /auth/jwt/login where the decrypted challenge has version missing, non-string, or a value different from self::PROTOCOL_VERSION (e.g. client built for an older protocol 'v1' vs 'v2').
Common situations: Client SDK and passbolt server versions out of sync after a server upgrade; hand-rolled login scripts hardcoding an old version string; typo like 'V1' (case-sensitive comparison).
Related errors
- The challenge cannot be decrypted.
- The challenge is invalid. Deserialization failed.
- The challenge is invalid. Validation Failed.
- The domain is invalid.
- Attempt to access an expired verify token.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/8538b736a36f8db0.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltCe/JwtAuthentication/src/Authenticator/GpgJwtAuthenticator.php:430
/**
* @param mixed $armoredChallenge challenge
* @throws \InvalidArgumentException if armored challenge is invalid
* @return void
*/
public function assertArmoredChallenge(mixed $armoredChallenge): void
{
$this->assertGpgMessageIsValid($this->gpg, $armoredChallenge, __('The user challenge is missing or invalid.'));
}
/**
* @param mixed $version version
* @throws \Exception if version is not supported
* @return void
*/
public function assertVersion(mixed $version): void
{
if (!isset($version) || !is_string($version) || $version !== self::PROTOCOL_VERSION) {
throw new Exception(__('The version is invalid.'));
}
}
/**
* Assert domain
*
* @param mixed $domain domain
* @return void
* @throws \Passbolt\JwtAuthentication\Error\Exception\Challenge\InvalidDomainException if domain is invalid
*/
public function assertDomain(mixed $domain): void
{
if (!isset($domain) || !is_string($domain)) {
throw new InvalidDomainException(__('The domain is invalid.'));
}
if (rtrim($domain, '/') !== rtrim(Router::url('/', true), '/')) {
$expect = rtrim(Router::url('/', true));View on GitHub (pinned to 31c1bbc10f)