passbolt/passbolt_api · error · BadRequestException

Record not found in table "sso_auth_tokens"

Error message

Record not found in table "sso_auth_tokens"

What it means

Thrown when SsoAuthenticationTokenGetService::getOrFail() (with type TYPE_SSO_RECOVER) cannot find the supplied token, and the raw RecordNotFoundException message naming the sso_auth_tokens table is propagated as a BadRequestException. Unlike the success controllers, this message is not rewritten, so the table name appears in the response.

Solutions

  1. Use the exact token value from the current SSO recover initiation (verify_token/start response)
  2. Restart the recover flow to mint a new token if the old one was consumed
  3. Check sso_auth_tokens for the token row, its type and active flag when debugging
  4. Ensure only one client/consumer processes the flow at a time to avoid consuming the token twice

Example fix

// before
POST /sso/recover/start {"username":"...", "token":"<unknown>"}  -> 400 Record not found in table "sso_auth_tokens"
// after: obtain a live token first
POST /sso/recover/start {"username":"..."} -> follow returned flow -> present the token from that response
Defensive patterns

Strategy: validation

Validate before calling

if (typeof token !== 'string' || token.length === 0) {
  throw new Error('token is required for SSO recover start completion');
}

Type guard

const hasValidToken = (data) => typeof data?.token === 'string' && data.token.length > 0;

Try / catch

try {
  await ssoRecoverStartComplete({username, token});
} catch (e) {
  if (e.message.includes('sso_auth_tokens')) await restartRecoverFlow();
  else throw e;
}

Prevention

When it happens

Trigger: POST /sso/recover/start with a token that does not exist, was already consumed, is of the wrong type, or was deleted; token field valid per form but unknown to the DB.

Common situations: Client fabricates or typos the token; token consumed by a prior start attempt; testing with an expired link's token; DB truncation/cleanup between test runs.

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/48418688dd2a5c37. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltEe/SsoRecover/src/Controller/RecoverStartController.php:78

            $settingsDto = (new SsoSettingsGetService())->getActiveOrFail();
        } catch (RecordNotFoundException $e) {
            throw new BadRequestException(__('No valid SSO settings found.'), null, $e);
        }

        $form = new SsoRecoverStartForm();
        if (!$form->execute($this->getRequest()->getData())) {
            throw new FormValidationException(__('Could not validate the SSO recover request.'), $form);
        }

        // Assert & consume sso auth token
        $ssoAuthService = new SsoAuthenticationTokenGetService();
        try {
            $ssoAuthToken = $ssoAuthService->getOrFail(
                $form->getData('token'),
                SsoState::TYPE_SSO_RECOVER
            );
        } catch (RecordNotFoundException $e) {
            throw new BadRequestException($e->getMessage(), null, $e);
        }

        $uac = new ExtendedUserAccessControl(
            Role::GUEST,
            $ssoAuthToken->user_id,
            null,
            $this->User->ip(),
            $this->User->userAgent()
        );
        $ssoAuthService->assertAndConsume($ssoAuthToken, $uac, $settingsDto->id);

        $url = (new SsoRecoverStartService())->generateAndGetRecoverUrl($ssoAuthToken->user_id);

        $this->success(__('The operation was successful.'), ['url' => $url]);
    }
}

View on GitHub (pinned to 31c1bbc10f)