{"record":{"id":"14ea49c26bdac5c6","repo":"passbolt/passbolt_api","slug":"error-azureprovider","errorCode":null,"errorMessage":"error","messagePattern":"error","errorType":"http","errorClass":"AzureException","httpStatus":null,"severity":"error","filePath":"plugins/PassboltEe/Sso/src/Utility/Azure/Provider/AzureProvider.php","lineNumber":143,"sourceCode":"    /**\n     * @inheritDoc\n     */\n    protected function createResourceOwner(array $response, AccessToken $token): ResourceOwnerInterface\n    {\n        return new AzureResourceOwner($response, $this->emailClaim);\n    }\n\n    /**\n     * @inheritDoc\n     */\n    protected function checkResponse(ResponseInterface $response, $data): void\n    {\n        if (empty($data['error'])) {\n            return;\n        }\n\n        if (is_string($data['error']) && isset($data['error_description']) && is_string($data['error_description'])) {\n            throw new AzureException($data['error'], $data['error_description']);\n        } else {\n            throw new IdentityProviderException(\n                $response->getReasonPhrase(),\n                $response->getStatusCode(),\n                (string)$response->getBody()\n            );\n        }\n    }\n\n    /**\n     * Get JWT verification keys from Azure Active Directory.\n     *\n     * @return array\n     */\n    public function getJwtVerificationKeys(): array\n    {\n        $openIdConfiguration = $this->getOpenIdConfiguration();\n        $keysUri = $openIdConfiguration['jwks_uri'];","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/Sso/src/Utility/Azure/Provider/AzureProvider.php#L125-L161","documentation":"AzureProvider::checkResponse() inspects the token/authorization response body; when it contains an 'error' field that is a string with a string 'error_description', it throws AzureException with the provider's error code. The message shown ('error') is the raw value Azure returned in the `error` field.","triggerScenarios":"Exchanging the authorization code at the Azure token endpoint and receiving e.g. {\"error\":\"invalid_grant\", ...} — expired/replayed code, wrong client secret, redirect URI mismatch, or MSA vs AAD account mismatch.","commonSituations":"Code replayed after browser refresh/back; client secret expired or rotated in Azure portal; redirect URI not exactly matching the registered one (scheme/trailing slash); user signed in with a personal account on an AAD-only app.","solutions":["Read error_description from the exception/log for Azure's precise reason","Restart the OAuth flow — codes are single-use and expire in minutes","Verify client ID, secret, and redirect URI against the Azure app registration","Ensure the app's supported account types match the user's account kind (AAD vs MSA)"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// pre-flight: verify settings configured && code present in request before exchange\nif (empty($code) || empty($settings->clientId) || empty($settings->redirectUri)) { abort(); }","typeGuard":null,"tryCatchPattern":"try { $token = $provider->getAccessToken('authorization_code', ['code' => $code]); } catch (AzureException $e) { Log::warning('Azure token exchange: ' . $e->getMessage() . ' / ' . $e->getDescription()); return redirect('/sso/restart'); }","preventionTips":["Never reuse or refresh authorization codes","Rotate client secrets before expiry and update passbolt settings","Match redirect URIs byte-for-byte with the app registration","Ensure account type (AAD vs personal) matches app registration"],"tags":["sso","azure-ad","oauth2","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"}