{"record":{"id":"5c89de929b2d7f63","repo":"passbolt/passbolt_api","slug":"failed-to-obtain-smtp-oauth2-access-token","errorCode":null,"errorMessage":"Failed to obtain SMTP OAuth2 access token.","messagePattern":"Failed to obtain SMTP OAuth2 access token\\.","errorType":"exception","errorClass":"InternalErrorException","httpStatus":500,"severity":"error","filePath":"plugins/PassboltCe/SmtpSettings/src/Service/SmtpOauthExchangeOnlineService.php","lineNumber":124,"sourceCode":"     * @throws \\Cake\\Http\\Exception\\InternalErrorException If the token request fails.\n     * @see https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#get-a-token\n     */\n    public function getAccessToken(): string\n    {\n        $tokenUrl = str_replace('__TENANT_ID__', $this->tenantId, self::LOGIN_TOKEN_URL);\n\n        $response = $this->httpClient->post($tokenUrl, [\n            'grant_type' => 'client_credentials',\n            'client_id' => $this->clientId,\n            'client_secret' => $this->clientSecret,\n            'scope' => self::SCOPE,\n        ]);\n\n        if (!$response->isOk()) {\n            $body = $response->getJson();\n            $error = $body['error_description'] ?? $body['error'] ?? 'Unknown error';\n            Log::error(sprintf('SMTP OAuth2 token fetch failed: %s', $error));\n            throw new InternalErrorException(__('Failed to obtain SMTP OAuth2 access token.'));\n        }\n\n        $body = $response->getJson();\n        if (empty($body['access_token'])) {\n            throw new InternalErrorException(\n                __('SMTP OAuth2 token response from Microsoft did not contain an access token.')\n            );\n        }\n\n        return $body['access_token'];\n    }\n\n    /**\n     * Get the OAuth2 username (email address of the sending mailbox).\n     *\n     * @return string\n     */\n    public function getUsername(): string","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltCe/SmtpSettings/src/Service/SmtpOauthExchangeOnlineService.php#L106-L142","documentation":"Thrown by SmtpOauthExchangeOnlineService::getAccessToken when the HTTP token request to Microsoft's OAuth2 token endpoint returns a non-OK response. The error_description/error from Microsoft is logged, then a generic InternalErrorException (500) is thrown.","triggerScenarios":"POST to https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token fails: invalid client credentials (AADSTS7000215), wrong tenant, unauthorized client for SMTP (AADSTS700027 / missing SMTP.Send scope consent), network errors producing non-OK responses, or expired client secret.","commonSituations":"Expired or rotated Azure client secret; app not granted/consented to SMTP.Send or full_access_as_app permission; tenant_id GUID whose app belongs to another tenant; firewall blocking outbound calls to login.microsoftonline.com.","solutions":["Check the passbolt error log for 'SMTP OAuth2 token fetch failed' to read Microsoft's error_description (e.g. AADSTS code).","Verify client_id/client_secret/tenant_id are correct and the client secret has not expired in Azure App registrations > Certificates & secrets.","Ensure the app has the SMTP.Send (or full_access_as_app for Exchange Online) application permission with admin consent granted.","Confirm the server can reach login.microsoftonline.com (outbound HTTPS/firewall/DNS)."],"exampleFix":"// before: secret expired, AADSTS7000215\nthrow new InternalErrorException(__('Failed to obtain SMTP OAuth2 access token.'));\n\n// after: rotate the secret in Azure Portal, update settings, retry\n// Azure Portal > App registrations > Certificates & secrets > New client secret\n// then re-save smtp settings with the new secret","handlingStrategy":"try-catch","validationCode":"// preflight: verify credentials can reach Microsoft before configuring\n$resp = $http->post(\"https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token\", [\n    'grant_type' => 'client_credentials', 'client_id' => $clientId,\n    'client_secret' => $secret, 'scope' => 'https://outlook.office365.com/.default',\n]);\nif (!$resp->isOk()) { /* fix config before wiring into passbolt */ }","typeGuard":null,"tryCatchPattern":"try {\n    $token = $service->getAccessToken();\n} catch (InternalErrorException $e) {\n    // read application logs for the AADSTS error_description and fix Azure config\n    Log::error('OAuth token exchange failed; check Azure secret/consent.');\n}","preventionTips":["Monitor Azure client secret expiry and rotate before it lapses.","Grant admin consent for SMTP.Send / full_access_as_app on the app registration.","Ensure outbound HTTPS to login.microsoftonline.com is allowed from the server.","Read the logged error_description — passbolt logs the exact AADSTS reason."],"tags":["oauth","network","azure","smtp","http-error"],"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"}