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

  1. Ensure the OIDC scopes requested include email (and for Azure, User.Read or email/profile) in the SSO settings form.
  2. In the IdP admin console, verify the user account has an email and that the email claim is included in the token/userinfo response.
  3. If using a custom provider, make its resource owner class extract and return the email from the token payload correctly.
  4. Check the provider's token response (logged via Log::error on IdentityProviderException) to see which claims are actually returned.
  5. 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

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


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)