passbolt/passbolt_api · error · BadRequestException
Single sign-on failed. Invalid nonce.
Error message
Single sign-on failed. Invalid nonce.
What it means
The OIDC nonce sent in the initial authorization request is stored in the SsoState record. When the provider returns the id_token, its nonce claim must match the stored value; a mismatch means the response may be replayed or not originate from this login attempt, so a BadRequestException is thrown.
Solutions
- Retry the SSO login from a fresh page (avoid stale bookmarked/cached authorization URLs).
- Check the provider's id_token JWT payload (decode it) to confirm the nonce claim is present and matches what was sent.
- Verify the provider implementation's getNonce() parses the id_token nonce claim correctly (custom providers).
- Clear cached SSO callback redirects and ensure only one login tab is used per attempt.
Example fix
// before (custom resource owner never reads nonce claim)
public function getNonce(): ?string { return null; }
// after
public function getNonce(): ?string { return $this->data['nonce'] ?? null; } Defensive patterns
Strategy: retry
Validate before calling
// decode id_token locally before asserting
$claims = json_decode(base64_decode(explode('.', $idToken)[1]), true);
if (($claims['nonce'] ?? null) !== $ssoState->nonce) {
// restart the SSO flow with a fresh state
} Type guard
function nonceMatches(?string $idTokenNonce, SsoState $state): bool {
return is_string($idTokenNonce) && hash_equals($state->nonce, $idTokenNonce);
} Try / catch
try {
$uac = $service->assertStateCodeAndGetUac($state, $code, ...);
} catch (BadRequestException $e) {
if (str_contains($e->getMessage(), 'Invalid nonce')) {
// start a brand-new SSO attempt (fresh authorization URL)
}
throw $e;
} Prevention
- Never bookmark or reuse old authorization URLs; always start a fresh SSO attempt.
- Complete the flow in a single tab to avoid crossed states.
- Ensure custom provider classes read the nonce claim from the id_token.
- Retry promptly; don't let state tokens linger before callback.
When it happens
Trigger: During assertResourceOwnerAgainstSsoState, resourceOwner->getNonce() differs from $ssoState->nonce — e.g. the id_token nonce claim is missing/different, or the state token was reused from a previous login attempt.
Common situations: Provider does not echo the nonce in the id_token; the browser replayed an old authorization URL or a cached callback; multiple tabs started different SSO flows and states got crossed; provider SDK's resource-owner nonce extraction returns null.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- CSRF issue. The state in request data does not match with…
- CSRF issue. The state in URL and Cookies do not match.
- Single sign-on failed. The
- Single sign-on failed. You must authenticate with Azure…
- The SSO state is invalid.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/882bc924763e627e.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltEe/Sso/src/Service/Sso/AbstractSsoService.php:305
if (mb_strtolower($resourceOwner->getEmail()) !== mb_strtolower($user->username)) {
$msg = __('Single sign-on failed.') . ' ' . __('Username mismatch.');
throw new BadRequestException($msg);
}
}
/**
* @param \Passbolt\Sso\Utility\OpenId\SsoResourceOwnerInterface $resourceOwner Resource owner.
* @param \Passbolt\Sso\Model\Entity\SsoState $ssoState SSO state.
* @return void
* @throws \Cake\Http\Exception\BadRequestException if the assertion failed
*/
public function assertResourceOwnerAgainstSsoState(
SsoResourceOwnerInterface $resourceOwner,
SsoState $ssoState
): void {
if ($ssoState->nonce !== $resourceOwner->getNonce()) {
$msg = __('Single sign-on failed.') . ' ' . __('Invalid nonce.');
throw new BadRequestException($msg);
}
}
/**
* @param string $state uuid
* @param \App\Utility\ExtendedUserAccessControl $uac extend user access control
* @param string $settingsId uuid
* @param string $type Type of state.
* @return \Passbolt\Sso\Model\Entity\SsoState
*/
public function createSsoState(
string $state,
ExtendedUserAccessControl $uac,
string $settingsId,
string $type
): SsoState {
return (new SsoStatesSetService())->create(
$this->nonce,View on GitHub (pinned to 31c1bbc10f)