{"record":{"id":"6ff393ba0b4ab00c","repo":"passbolt/passbolt_api","slug":"single-sign-on-failed-provider-error-0","errorCode":null,"errorMessage":"Single sign-on failed. Provider error: \"{0}\"","messagePattern":"Single sign-on failed\\. Provider error: \"(.+?)\"","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltEe/Sso/src/Service/Sso/AbstractSsoService.php","lineNumber":261,"sourceCode":"        try {\n            // Try to get an access token using the authorization code grant.\n            /** @var \\League\\OAuth2\\Client\\Token\\AccessToken $accessToken */\n            $accessToken = $this->provider->getAccessToken('authorization_code', ['code' => $code]);\n\n            // Using the access token id_token, we may look up details about the resource owner.\n            $resourceOwner = $this->provider->getResourceOwner($accessToken);\n        } catch (IdentityProviderException $exception) {\n            $msg = \"Error while getting access token. Message: {$exception->getMessage()}, \";\n            if (!is_string($exception->getResponseBody())) {\n                $msg .= 'Response: ' . json_encode($exception->getResponseBody());\n            } else {\n                $msg .= \"Response: {$exception->getResponseBody()}\";\n            }\n\n            Log::error($msg);\n\n            $msg = __('Single sign-on failed.') . ' ' . __('Provider error: \"{0}\"', $exception->getMessage());\n            throw new BadRequestException($msg, 400, $exception);\n        }\n\n        // Helper for developers working on new providers\n        if (!($resourceOwner instanceof SsoResourceOwnerInterface)) {\n            $msg = 'Provider must return a ResourceOwner that implements ResourceOwnerWithEmailInterface.';\n            throw new InternalErrorException($msg);\n        }\n\n        $email = $resourceOwner->getEmail();\n        if (!isset($email) || !is_string($email) || !EmailValidationRule::check($email)) {\n            $msg = __('Single sign-on failed.') . ' ' . __('Email not provided by provider.');\n            throw new BadRequestException($msg);\n        }\n\n        return $resourceOwner;\n    }\n\n    /**","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/Sso/src/Service/Sso/AbstractSsoService.php#L243-L279","documentation":"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.","triggerScenarios":"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.","commonSituations":"Clock skew invalidating tokens; Azure AD tenant/app config changed; wrong environment credentials (staging secret on production); replayed callback (code single-use); provider outage.","solutions":["Read the embedded provider error in the message and check the passbolt error logs (Log::error includes response body) for details","Verify SSO settings: client id, client secret, redirect URI, token endpoint match the IdP app configuration","Regenerate the client secret if rotated on the provider, then update passbolt SSO settings","Restart the SSO flow with a fresh authorization code (codes are single-use and short-lived)","Check network egress from the passbolt server to the IdP token endpoint"],"exampleFix":"// before\n'client_secret' => 'old-revoked-secret'\n// after\n'client_secret' => '<new-secret-from-provider>' // then re-run `passbolt sso_settings set`","handlingStrategy":"try-catch","validationCode":"// pre-flight: verify settings and fresh code\nif (!Validation::uuid($settingsId) || empty($code)) {\n    throw new \\InvalidArgumentException('SSO settings id and fresh auth code required');\n}","typeGuard":null,"tryCatchPattern":"try {\n    $owner = $service->getResourceOwnerAndAssertAgainstUser($provider, $code, $ip, $ua);\n} catch (BadRequestException $e) {\n    Log::error('SSO provider error: ' . $e->getMessage());\n    // retry with a fresh authorization code or fix provider settings\n}","preventionTips":["Never reuse authorization codes; always restart the OAuth flow","Keep provider client id/secret/redirect URI in sync with the IdP app","Monitor server logs for the full provider response body","Watch for provider-side secret rotation and config changes"],"tags":["sso","oauth2","identity-provider","token-exchange"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}