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

  1. Restart the account recovery flow to generate a fresh token and retry immediately
  2. Verify the token matches the same user_id in the request
  3. 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

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

Related errors


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 data

View on GitHub (pinned to 31c1bbc10f)