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
- Use the exact token value from the current SSO recover initiation (verify_token/start response)
- Restart the recover flow to mint a new token if the old one was consumed
- Check sso_auth_tokens for the token row, its type and active flag when debugging
- 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
- Only use tokens returned by the current flow response
- Assume tokens are single-use; request a new one after consumption
- Persist the token securely between steps of the flow
- Do not run duplicate parallel recover attempts for the same user
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
- The authentication token does not exist or has been deleted.
- No default expiry or expiry for token type
- The authentication token does not exist.
- The authentication token does not exist.
- The authentication token has been expired.
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)