{"record":{"id":"d4168ebb35bfdcba","repo":"passbolt/passbolt_api","slug":"smtp-oauth2-token-response-from-microsoft-did-not-contain-an","errorCode":null,"errorMessage":"SMTP OAuth2 token response from Microsoft did not contain an access token.","messagePattern":"SMTP OAuth2 token response from Microsoft did not contain an access token\\.","errorType":"exception","errorClass":"InternalErrorException","httpStatus":500,"severity":"error","filePath":"plugins/PassboltCe/SmtpSettings/src/Service/SmtpOauthExchangeOnlineService.php","lineNumber":129,"sourceCode":"        $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\n    {\n        return $this->username;\n    }\n\n    /**","sourceCodeStart":111,"sourceCodeEnd":147,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltCe/SmtpSettings/src/Service/SmtpOauthExchangeOnlineService.php#L111-L147","documentation":"Thrown by SmtpOauthExchangeOnlineService::getAccessToken when Microsoft's token endpoint responds HTTP 200 but the JSON body has no `access_token` field. The exchange was technically acknowledged yet produced no usable token, so an InternalErrorException (500) is thrown.","triggerScenarios":"Microsoft returns 200 with a body lacking access_token — e.g. an unexpected response shape, a proxy returning an HTML page with 200, or grant/param mismatch that Microsoft answers without a token.","commonSituations":"Corporate proxy/SSL interception returning 200 HTML; misconfigured token URL pointing at a non-token endpoint; Microsoft API changes; response body not being the expected JSON grant structure.","solutions":["Inspect/log the actual response body from Microsoft to see what was returned instead of an access_token.","Verify the token URL is https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token and grant_type is client_credentials with scope https://outlook.office365.com/.default.","Check for proxies or SSL-inspection appliances rewriting the response, and allowlist the Microsoft endpoint.","Retry after confirming credentials; if persistent, capture the request with a test script using curl to compare payloads."],"exampleFix":"// before (wrong scope, no token returned)\n'scope' => 'https://graph.microsoft.com/.default'\n\n// after (scope matching SMTP send via Exchange Online)\n'scope' => 'https://outlook.office365.com/.default'","handlingStrategy":"try-catch","validationCode":"$resp = $http->post($tokenUrl, $params);\n$body = $resp->getJson();\nif (!isset($body['access_token'])) {\n    // unexpected shape; inspect $body before relying on passbolt\n}","typeGuard":"function hasAccessToken(array $body): bool {\n    return isset($body['access_token']) && is_string($body['access_token']) && $body['access_token'] !== '';\n}","tryCatchPattern":"try {\n    $token = $service->getAccessToken();\n} catch (InternalErrorException $e) {\n    // log raw token response body; check proxy/SSL interception and token URL/scope\n}","preventionTips":["Allowlist login.microsoftonline.com from TLS-inspecting proxies.","Use the exact token URL pattern /oauth2/v2.0/token with the correct tenant.","Use scope https://outlook.office365.com/.default for Exchange SMTP.","Test the token request with curl before configuring passbolt."],"tags":["oauth","smtp","azure","unexpected-response","http"],"backgroundTag":"unexpected-response-shape","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"}