passbolt/passbolt_api · error · BadRequestException
Single sign-on failed. Email not provided by provider.
Error message
Single sign-on failed. Email not provided by provider.
What it means
Passbolt's SSO service exchanges the OAuth2 authorization code with the identity provider and fetches the resource owner (user info). Before using it, it validates that the returned resource owner carries a valid email address (isset, string, and passes EmailValidationRule). If the provider response has no usable email, a BadRequestException is thrown because Passbolt identifies users by email and cannot proceed.
Solutions
- Ensure the OIDC scopes requested include email (and for Azure, User.Read or email/profile) in the SSO settings form.
- In the IdP admin console, verify the user account has an email and that the email claim is included in the token/userinfo response.
- If using a custom provider, make its resource owner class extract and return the email from the token payload correctly.
- Check the provider's token response (logged via Log::error on IdentityProviderException) to see which claims are actually returned.
- Use the SSO dry-run endpoint to test the settings before saving and confirm the email is returned.
Example fix
// before (IdP app config missing scopes) scope: 'openid' // after scope: 'openid email profile'
Defensive patterns
Strategy: validation
Validate before calling
$email = $resourceOwner->getEmail();
if (!is_string($email) || !EmailValidationRule::check($email)) {
// abort flow before calling passbolt SSO verify, fix IdP claims/scopes
} Type guard
function hasValidEmail(object $ro): bool {
return $ro instanceof SsoResourceOwnerInterface
&& is_string($ro->getEmail())
&& filter_var($ro->getEmail(), FILTER_VALIDATE_EMAIL) !== false;
} Try / catch
try {
$resourceOwner = $service->getResourceOwner($code);
} catch (BadRequestException $e) {
if (str_contains($e->getMessage(), 'Email not provided')) {
// surface hint: check IdP email claim / scopes
}
throw $e;
} Prevention
- Always request 'openid email profile' (and Azure User.Read) scopes in SSO settings.
- Use the SSO dry-run to confirm the provider returns an email before activating settings.
- Verify every user in the IdP directory has an email set.
- Check the email claim is not filtered out by conditional access or privacy policies.
When it happens
Trigger: The IdP's userinfo/id_token payload lacks an email claim, returns a non-string value, or an email that fails validation (e.g. empty, malformed). Happens after getAccessToken succeeds in getResourceOwner during the SSO callback (retrieve/dry-run flows).
Common situations: Azure AD / AD FS app registration does not grant email or User.Read scopes; the user has no email configured in the IdP directory; email claims suppressed by conditional access policies or privacy settings; custom provider integration whose resource-owner mapping omits the email field.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- AccessToken should be an instance of BaseIdToken class.
- Cannot parse JWKS endpoint response.
- Could not save the SSO state, invalid nonce.
- Could not validate the SSO recover request.
- $data['error'] (dynamic provider error)
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/e37dd967ca29efb7.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltEe/Sso/src/Service/Sso/AbstractSsoService.php:273
$msg .= "Response: {$exception->getResponseBody()}";
}
Log::error($msg);
$msg = __('Single sign-on failed.') . ' ' . __('Provider error: "{0}"', $exception->getMessage());
throw new BadRequestException($msg, 400, $exception);
}
// Helper for developers working on new providers
if (!($resourceOwner instanceof SsoResourceOwnerInterface)) {
$msg = 'Provider must return a ResourceOwner that implements ResourceOwnerWithEmailInterface.';
throw new InternalErrorException($msg);
}
$email = $resourceOwner->getEmail();
if (!isset($email) || !is_string($email) || !EmailValidationRule::check($email)) {
$msg = __('Single sign-on failed.') . ' ' . __('Email not provided by provider.');
throw new BadRequestException($msg);
}
return $resourceOwner;
}
/**
* @param \Passbolt\Sso\Utility\OpenId\SsoResourceOwnerInterface $resourceOwner user
* @param \App\Model\Entity\User $user user
* @return void
* @throws \Cake\Http\Exception\BadRequestException if the assertion failed
*/
public function assertResourceOwnerAgainstUser(SsoResourceOwnerInterface $resourceOwner, User $user): void
{
if (mb_strtolower($resourceOwner->getEmail()) !== mb_strtolower($user->username)) {
$msg = __('Single sign-on failed.') . ' ' . __('Username mismatch.');
throw new BadRequestException($msg);
}
}View on GitHub (pinned to 31c1bbc10f)