passbolt/passbolt_api · error · OAuth2Exception
$data['error'] (dynamic provider error)
Error message
$data['error'] (dynamic provider error)
What it means
OAuth2Provider::checkResponse inspects the token endpoint's decoded JSON body; when it contains an `error` field the OAuth2 spec says the request failed. If both `error` and `error_description` are strings, the library throws OAuth2Exception carrying those dynamic provider-supplied values; otherwise it falls back to IdentityProviderException. The message text is thus dictated by the identity provider, not passbolt.
Solutions
- Read the OAuth2Exception error/error_description to identify the provider's exact reason (e.g. invalid_grant, invalid_client).
- Restart the SSO login flow to obtain a fresh authorization code — codes expire within minutes and are single-use.
- Verify the client id, client secret, and redirect URI registered in the provider admin console match passbolt's SSO settings exactly.
- Check server clock synchronization (NTP) since expired codes/timestamps commonly cause invalid_grant.
Defensive patterns
Strategy: try-catch
Validate before calling
$body = json_decode((string)$response->getBody(), true);
if (isset($body['error'])) {
// provider signalled failure; handle before constructing tokens
} Type guard
function hasOAuthError(?array $data): bool {
return !empty($data['error']);
} Try / catch
try {
$provider->checkResponse($response, $data);
} catch (OAuth2Exception $e) {
// $e->getError() / description: e.g. invalid_grant -> restart login flow
} Prevention
- Never reuse authorization codes; always start a fresh OAuth flow on failure
- Keep client id/secret/redirect URI in sync with the provider console
- Run NTP on servers to avoid invalid_grant from clock skew
When it happens
Trigger: Any OAuth2 token/authorization request whose HTTP response body parses to JSON containing a non-empty `error` key — e.g. code exchange with an expired/already-used authorization code, bad client_id/client_secret, or redirect_uri mismatch.
Common situations: Replaying an authorization code (they are single-use); clock skew invalidating codes; misconfigured client secret after rotating credentials in Azure/Google admin consoles; provider returning structured errors like `invalid_grant`.
Related errors
- $response->getReasonPhrase() (dynamic provider error)
- $data['error'] (dynamic provider error)
- $response->getReasonPhrase() (dynamic provider error)
- Single sign-on failed. Provider error
- AccessToken should be an instance of BaseIdToken class.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/7d4d1520508eb2b5.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltEe/Sso/src/Utility/OAuth2/Provider/OAuth2Provider.php:57
parent::__construct($options, $collaborators);
$this->grantFactory->setGrant('jwt_bearer', new JwtBearer());
}
/**
* {@inheritDoc}
*
* @throws \Passbolt\Sso\Error\Exception\OAuth2Exception When error and error description is present
* @throws \League\OAuth2\Client\Provider\Exception\IdentityProviderException When unknown error faced
*/
protected function checkResponse(ResponseInterface $response, $data): void
{
if (empty($data['error'])) {
return;
}
if (is_string($data['error']) && isset($data['error_description']) && is_string($data['error_description'])) {
throw new OAuth2Exception($data['error'], $data['error_description']);
} else {
throw new IdentityProviderException(
$response->getReasonPhrase(),
$response->getStatusCode(),
(string)$response->getBody()
);
}
}
/**
* @inheritDoc
*/
protected function createResourceOwner(array $response, AccessToken $token): ResourceOwnerInterface
{
return new OAuth2ResourceOwner($response);
}
}
View on GitHub (pinned to 31c1bbc10f)