passbolt/passbolt_api · error · BadRequestException

The authentication token does not exist or has been deleted.

Error message

The authentication token does not exist or has been deleted.

What it means

Thrown by PingOneRecoverSuccessController::ssoRecoverSuccess when SsoAuthenticationTokenGetService::getActiveNotExpiredOrFail() cannot find an active, non-expired sso_auth_tokens record of type TYPE_SSO_RECOVER matching the token from the URL query. The RecordNotFoundException is wrapped in a BadRequestException with this message.

Solutions

  1. Restart the SSO recovery flow to obtain a fresh token link
  2. Verify the token query parameter is present and copied exactly from the email/link
  3. Do not reuse the success URL after the flow completed (token is single-use)
  4. Check sso_auth_tokens table for the token id and its active/expires fields when debugging

Example fix

// before: reused/consumed token
GET /sso/recover/success/pingone?token=<already-used>
// after
GET /recover/start -> new link -> GET /sso/recover/success/pingone?token=<new-token>
Defensive patterns

Strategy: validation

Validate before calling

if (!token || typeof token !== 'string' || token.length < 8) {
  throw new Error('Missing or malformed SSO recover token in URL.');
}

Type guard

const hasToken = (q) => typeof q.token === 'string' && q.token.length > 0;

Try / catch

try {
  await ssoRecoverSuccess(token);
} catch (e) {
  if (e.message.includes('does not exist')) {
    await restartRecoverFlow();
  } else throw e;
}

Prevention

When it happens

Trigger: Token query parameter missing, empty, mistyped, already consumed (single-use), or deleted; token belongs to a different type; recovery flow restarted so old token was replaced.

Common situations: User manually edits the callback URL; user completes recovery in one browser then reuses the link elsewhere; token consumed by an earlier duplicate callback request; database cleanup removed stale tokens.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at plugins/PassboltEe/SsoRecover/src/Controller/PingOne/PingOneRecoverSuccessController.php:54

        $this->Authentication->allowUnauthenticated(['ssoRecoverSuccess']);
    }

    /**
     * @return void
     */
    public function ssoRecoverSuccess(): void
    {
        if ($this->request->is('json')) {
            throw new BadRequestException(__('Ajax/Json request not supported.'));
        }

        $this->User->assertNotLoggedIn();
        $token = $this->getTokenFromUrlQuery();

        try {
            (new SsoAuthenticationTokenGetService())->getActiveNotExpiredOrFail($token, SsoState::TYPE_SSO_RECOVER);
        } catch (RecordNotFoundException $e) {
            throw new BadRequestException(
                __('The authentication token does not exist or has been deleted.'),
                null,
                $e
            );
        } catch (CustomValidationException $e) {
            throw new BadRequestException(
                __('The authentication token has been expired.'),
                null,
                $e
            );
        }

        $this->viewBuilder()
            ->setTheme('Passbolt/Sso')
            ->setLayout('default')
            ->setTemplatePath('success')
            ->setTemplate('stage3');
    }

View on GitHub (pinned to 31c1bbc10f)