passbolt/passbolt_api · error · BadRequestException

Single sign-on failed. Provider error

Error message

Single sign-on failed. Provider error: "{0}"

What it means

getResourceOwner() exchanges the OAuth code with the identity provider; when the League OAuth2 provider throws IdentityProviderException, the message is logged and re-thrown as BadRequestException 'Single sign-on failed. Provider error: "{0}"' embedding the provider's error message. It surfaces upstream IdP failures to the passbolt client.

Solutions

  1. Read the embedded provider error in the message and check the passbolt error logs (Log::error includes response body) for details
  2. Verify SSO settings: client id, client secret, redirect URI, token endpoint match the IdP app configuration
  3. Regenerate the client secret if rotated on the provider, then update passbolt SSO settings
  4. Restart the SSO flow with a fresh authorization code (codes are single-use and short-lived)
  5. Check network egress from the passbolt server to the IdP token endpoint

Example fix

// before
'client_secret' => 'old-revoked-secret'
// after
'client_secret' => '<new-secret-from-provider>' // then re-run `passbolt sso_settings set`
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-flight: verify settings and fresh code
if (!Validation::uuid($settingsId) || empty($code)) {
    throw new \InvalidArgumentException('SSO settings id and fresh auth code required');
}

Try / catch

try {
    $owner = $service->getResourceOwnerAndAssertAgainstUser($provider, $code, $ip, $ua);
} catch (BadRequestException $e) {
    Log::error('SSO provider error: ' . $e->getMessage());
    // retry with a fresh authorization code or fix provider settings
}

Prevention

When it happens

Trigger: Authorization code is invalid, expired, or already used; client_id/client_secret mismatch with the IdP; token endpoint URL wrong or unreachable; IdP rejects redirect_uri; account/tenant disabled on the provider side.

Common situations: Clock skew invalidating tokens; Azure AD tenant/app config changed; wrong environment credentials (staging secret on production); replayed callback (code single-use); provider outage.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/6ff393ba0b4ab00c. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltEe/Sso/src/Service/Sso/AbstractSsoService.php:261

        try {
            // Try to get an access token using the authorization code grant.
            /** @var \League\OAuth2\Client\Token\AccessToken $accessToken */
            $accessToken = $this->provider->getAccessToken('authorization_code', ['code' => $code]);

            // Using the access token id_token, we may look up details about the resource owner.
            $resourceOwner = $this->provider->getResourceOwner($accessToken);
        } catch (IdentityProviderException $exception) {
            $msg = "Error while getting access token. Message: {$exception->getMessage()}, ";
            if (!is_string($exception->getResponseBody())) {
                $msg .= 'Response: ' . json_encode($exception->getResponseBody());
            } else {
                $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;
    }

    /**

View on GitHub (pinned to 31c1bbc10f)