passbolt/passbolt_api · error · InvalidJwtKeyPairException
The JWT public key could not be read or is not valid.
Error message
The JWT public key could not be read or is not valid.
What it means
validateKeyPair() first checks is_readable() on the JWT public key path; if unreadable it throws this message. It also throws the identical message when getSecretKeySize() returns 0, i.e. the private key could not be read/parsed either. Both mean the installed JWT key pair files are missing, unreadable, or corrupt.
Solutions
- Generate the key pair: sudo -u www-data bin/cake passbolt create jwt_keys (or the pro equivalent)
- Verify both files exist and are non-empty: ls -la config/jwt/; head -1 config/jwt/jwt.public.key
- Fix permissions so the runtime user can read them: chown www-data:www-data config/jwt/*; chmod 640 config/jwt/*
- If files are empty/corrupt, delete them and regenerate the pair
Example fix
// before # empty jwt.public.key after failed generation // after rm config/jwt/jwt.public.key config/jwt/jwt.private.key sudo -u www-data bin/cake passbolt create jwt_keys
Defensive patterns
Strategy: validation
Validate before calling
$pub = Configure::read('passbolt.jwt.publicKeyPath') ?? CONFIG . 'jwt' . DS . 'jwt.public.key';
if (!is_readable($pub) || filesize($pub) === 0) { // regenerate key pair before calling validateKeyPair
} Type guard
if (!is_string($pub) || !is_file($pub) || !is_readable($pub)) { /* trigger regeneration */ } Try / catch
try { $service->validateKeyPair(); } catch (InvalidJwtKeyPairException $e) { // run bin/cake passbolt create jwt_keys as runtime user } Prevention
- Run `passbolt create jwt_keys` after every install/upgrade
- Persist config/jwt across deployments (volume/backup)
- Include JWT key readability in health checks
When it happens
Trigger: validateKeyPair() is called (via execute) and either config/jwt/jwt.public.key is not readable by the current user, or its content/secret key size is 0 (empty or unparseable key files).
Common situations: JWT keys never generated (`passbolt create jwt_keys` skipped after install/upgrade); files lost during deployment because config/jwt was not persisted; empty files from a partially failed generation; wrong user permissions after container restart.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- Cannot delete group user.
- Cannot update group user.
- Could not validate move data.
- Could not validate permission data.
- Could not validate permission history data.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/a92aeac57f610d75.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltCe/JwtAuthentication/src/Service/AccessToken/JwtKeyPairService.php:117
throw new InvalidJwtKeyPairException($e->getMessage());
}
}
/**
* Validate the key pair validity as defined by the public and secret services.
*
* @param string|null $uuid Uuid for testing aim
* @return object
* @throws \Passbolt\JwtAuthentication\Error\Exception\AccessToken\InvalidJwtKeyPairException
*/
public function validateKeyPair(?string $uuid = null): object
{
// Minimal size of the private key
$minSecretKeySize = JwtTokenCreateService::JWT_KEY_LENGTH;
$uuid = $uuid ?? UuidFactory::uuid();
try {
if (!is_readable($this->publicService->getKeyPath())) {
throw new Exception(__('The JWT public key could not be read or is not valid.'));
}
$publicKey = file_get_contents($this->publicService->getKeyPath());
$secretKeySize = $this->publicService->getSecretKeySize();
if ($secretKeySize === 0) {
throw new Exception(__('The JWT public key could not be read or is not valid.'));
}
if ($secretKeySize < $minSecretKeySize) {
throw new Exception(__(
'The JWT private key should be at least {0} bytes long.',
$this->secretService::JWT_KEY_LENGTH
));
}
$jwt = $this->secretService->createToken($uuid, '2 seconds');
return JWT::decode($jwt, new Key($publicKey, $this->secretService::JWT_ALG));View on GitHub (pinned to 31c1bbc10f)