passbolt/passbolt_api · error · CustomValidationException

The authentication token is not valid.

Error message

The authentication token is not valid.

What it means

consumeActiveNotExpiredOrFail atomically deactivates the token via setInactive(). If the update affects no rows, another request already consumed the token in a race, so this method throws CustomValidationException('The authentication token is not valid.') with a 'token.isActive' error mirroring getActiveOrFail's contract.

Solutions

  1. Treat the exception as a consumed-token case and show the user an 'already used link' message.
  2. Reload the setup/complete page to get a fresh token state and an accurate error.
  3. Idempotent-guard clients: disable the submit action after the first request.
  4. If the user is actually stuck, issue a new authentication token (e.g. resend setup email).
Defensive patterns

Strategy: try-catch

Try / catch

try {
    $token = $service->consumeActiveNotExpiredOrFail($token, $userId, $type);
} catch (CustomValidationException $e) {
    $errors = $e->getErrors();
    if (isset($errors['token']['isActive'])) {
        // token already consumed — show 'link already used' page
    }
}

Prevention

When it happens

Trigger: Two requests consume the same valid, unexpired register/complete-setup token concurrently and both pass getActiveNotExpiredOrFail; the loser's setInactive() returns false.

Common situations: User double-clicks a setup link or the setup page auto-submits while the user clicks manually; duplicated email-link deliveries; retries after slow responses.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/6589df0ec34f0e7b. Report an issue: GitHub.

Appendix: source

Thrown at src/Service/AuthenticationTokens/AuthenticationTokenConsumeService.php:57

     * @throws \Cake\Http\Exception\NotFoundException if token is not found
     * @throws \App\Error\Exception\CustomValidationException if the token is expired, inactive, or lost a concurrent-consume race
     * @throws \Cake\Http\Exception\BadRequestException if token id is not a valid uuid
     */
    public function consumeActiveNotExpiredOrFail(
        string $token,
        string $userId,
        string $type,
        ?string $expiry = null
    ): AuthenticationToken {
        $authenticationToken = (new AuthenticationTokenGetService())
            ->getActiveNotExpiredOrFail($token, $userId, $type, $expiry);

        /** @var \App\Model\Table\AuthenticationTokensTable $authTokensTable */
        $authTokensTable = $this->fetchTable('AuthenticationTokens');
        if (!$authTokensTable->setInactive($authenticationToken->token)) {
            // Lost the concurrent-consume race — mirror `getActiveOrFail`'s "already consumed" contract.
            $error = ['token' => ['isActive' => __('The token is already consumed.')]];
            throw new CustomValidationException(__('The authentication token is not valid.'), $error);
        }

        $authenticationToken->set('active', false);
        $authenticationToken->setDirty('active', false);

        return $authenticationToken;
    }
}

View on GitHub (pinned to 31c1bbc10f)