passbolt/passbolt_api · error · BadRequestException

The authentication token is not valid.

Error message

The authentication token is not valid.

What it means

Thrown by assertAndConsumeToken in RecoverAbortService when getActiveNotExpiredOrFail finds no active, unexpired RECOVER token matching the given token id and user id. The abort-recovery endpoint therefore rejects the request because the token is unknown, already used, expired, or belongs to another user.

Solutions

  1. Request a fresh recovery (start the recover flow again) to obtain a new active token and use that link
  2. Confirm the token id in the URL matches the latest recover email and the user id matches the token's user_id
  3. Check authentication_tokens for the row: verify type = RECOVER, active = 1, expired date in the future
  4. Enable debug logging (Configure read('debug')) to see the underlying getActiveNotExpiredOrFail failure reason
Defensive patterns

Strategy: validation

Validate before calling

const t = await getTokenRow(tokenId);
const valid = t && t.type === 'recover' && t.active && new Date(t.expired) > new Date() && t.user_id === userId;

Try / catch

try { await recoverAbort(userId, tokenId); }
catch (e) { if (isInvalidToken(e)) startNewRecoveryFlow(userId); else throw e; }

Prevention

When it happens

Trigger: POST /setup/recover/abort/{userId}/{tokenId} with an expired token, an already-consumed token, a token whose user_id does not match, or a malformed/nonexistent token id (wrong type).

Common situations: User clicking an old recover link after starting a newer recovery (older tokens invalidated/expired); token expired because the user waited too long; copying the wrong token id from logs; replaying an abort call twice.

Understand the failure class

Related errors


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

Appendix: source

Thrown at src/Service/Setup/RecoverAbortService.php:83

    /**
     * Return the token or fail
     *
     * @param string $token token.token
     * @param string $userId User ID
     * @return void
     * @throws \Cake\Http\Exception\BadRequestException if the token is missing, expired, invalid, or already consumed
     */
    protected function assertAndConsumeToken(string $token, string $userId): void
    {
        try {
            $tokenEntity = (new AuthenticationTokenGetService())
                ->getActiveNotExpiredOrFail($token, $userId, AuthenticationToken::TYPE_RECOVER);
        } catch (NotFoundException $exception) {
            if (Configure::read('debug')) {
                Log::error('getActiveNotExpiredOrFail() failed: ' . $exception->getMessage());
            }
            throw new BadRequestException(__('The authentication token is not valid.'));
        }

        /** @var \App\Model\Table\AuthenticationTokensTable $authenticationTokensTable */
        $authenticationTokensTable = $this->fetchTable('AuthenticationTokens');
        if (!$authenticationTokensTable->setInactive($tokenEntity->token)) {
            // Lost the concurrent-consume race.
            throw new BadRequestException(__('The authentication token is not valid.'));
        }
    }

    /**
     * Return the user for matching the requesting id
     *
     * @param string $userId the user uuid
     * @throws \Cake\Http\Exception\BadRequestException if the user id is not a valid uuid
     * @throws \Cake\Http\Exception\BadRequestException if the user was deleted or has not completed the setup
     * @return \App\Model\Entity\User
     */

View on GitHub (pinned to 31c1bbc10f)