passbolt/passbolt_api · error · Cake\Http\Exception\BadRequestException
The authentication token is not valid or has expired.
Error message
The authentication token is not valid or has expired.
What it means
BadRequestException from getAndAssertToken when the supplied recover token either does not exist, does not belong to the user, is of the wrong type, is inactive, or has expired. AuthenticationTokenGetService::getActiveNotExpiredOrFail() raised a NotFoundException which is translated into this message.
Solutions
- Restart the account recovery flow to generate a fresh token and retry immediately
- Verify the token matches the same user_id in the request
- Check the authentication_tokens table: row must have type 'recover', active=true, and created within expiry window
Example fix
// before oldToken = <token from a completed recovery attempt> // inactive -> 400 // after start a new recovery flow, use the newly issued token in the create call
Defensive patterns
Strategy: retry
Validate before calling
// Pre-check token state client-side: it must be recent, type 'recover', and not previously used
Type guard
null
Try / catch
try { $service->create($data); } catch (BadRequestException $e) { if (str_contains($e->getMessage(), 'not valid or has expired')) { restartRecoveryFlow(); } } Prevention
- Never reuse a token from a prior recovery attempt (they are deactivated on use)
- Use the token promptly; tokens expire
- Ensure client and server point to the same passbolt instance/database
- Catch this error and automatically restart the recovery flow
When it happens
Trigger: POST /account-recovery/requests with an authentication_token.token that is not found via getActiveNotExpiredOrFail($token, $userId, TYPE_RECOVER): wrong id, already consumed/deactivated by a previous recovery request, expired, or belonging to another user.
Common situations: Reusing a token from an earlier recovery attempt (tokens are deactivated after use); user waited past token expiry; copy/paste truncation of the token; environment mismatch (token created on another instance/database).
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- An authentication token should be provided.
- Account recovery case must be a string.
- Account recovery is disabled.
- Account recovery reason not supported.
- Conflicting authentication parameters, provide…
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/2b444f6bae26b7d2.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryRequests/AccountRecoveryRequestCreateService.php:173
*
* @param string $userId the user uuid the token belongs to
* @throws \Cake\Http\Exception\BadRequestException if no authentication token was provided
* @throws \Cake\Http\Exception\BadRequestException if the authentication token is not a uuid
* @throws \Cake\Http\Exception\BadRequestException if the authentication token is expired or invalid
* @return \App\Model\Entity\AuthenticationToken
*/
protected function getAndAssertToken(string $userId): AuthenticationToken
{
$token = $this->getData('authentication_token.token');
if (!isset($token)) {
throw new BadRequestException(__('An authentication token should be provided.'));
}
try {
$tokenEntity = (new AuthenticationTokenGetService())
->getActiveNotExpiredOrFail($token, $userId, AuthenticationToken::TYPE_RECOVER);
} catch (NotFoundException $exception) {
throw new BadRequestException(__('The authentication token is not valid or has expired.'));
}
// Deactivate all previous active tokens
$this->AuthenticationTokens->updateQuery()
->set(['active' => false])
->where([
'id <>' => $tokenEntity->id,
'active' => true,
'type' => AuthenticationToken::TYPE_RECOVER,
'user_id' => $userId,
])
->execute();
return $tokenEntity;
}
/**
* @param array $data user provided dataView on GitHub (pinned to 31c1bbc10f)