passbolt/passbolt_api · error · InternalErrorException
SMTP OAuth2 token response from Microsoft did not contain…
Error message
SMTP OAuth2 token response from Microsoft did not contain an access token.
What it means
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.
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.
Example fix
// before (wrong scope, no token returned) 'scope' => 'https://graph.microsoft.com/.default' // after (scope matching SMTP send via Exchange Online) 'scope' => 'https://outlook.office365.com/.default'
Defensive patterns
Strategy: try-catch
Validate before calling
$resp = $http->post($tokenUrl, $params);
$body = $resp->getJson();
if (!isset($body['access_token'])) {
// unexpected shape; inspect $body before relying on passbolt
} Type guard
function hasAccessToken(array $body): bool {
return isset($body['access_token']) && is_string($body['access_token']) && $body['access_token'] !== '';
} Try / catch
try {
$token = $service->getAccessToken();
} catch (InternalErrorException $e) {
// log raw token response body; check proxy/SSL interception and token URL/scope
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Client ID should be a valid UUID.
- Failed to obtain SMTP OAuth2 access token.
- Tenant ID should be a valid UUID.
- Ajax/Json request not supported.
- Ajax/Json request not supported.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/d4168ebb35bfdcba.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltCe/SmtpSettings/src/Service/SmtpOauthExchangeOnlineService.php:129
$tokenUrl = str_replace('__TENANT_ID__', $this->tenantId, self::LOGIN_TOKEN_URL);
$response = $this->httpClient->post($tokenUrl, [
'grant_type' => 'client_credentials',
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'scope' => self::SCOPE,
]);
if (!$response->isOk()) {
$body = $response->getJson();
$error = $body['error_description'] ?? $body['error'] ?? 'Unknown error';
Log::error(sprintf('SMTP OAuth2 token fetch failed: %s', $error));
throw new InternalErrorException(__('Failed to obtain SMTP OAuth2 access token.'));
}
$body = $response->getJson();
if (empty($body['access_token'])) {
throw new InternalErrorException(
__('SMTP OAuth2 token response from Microsoft did not contain an access token.')
);
}
return $body['access_token'];
}
/**
* Get the OAuth2 username (email address of the sending mailbox).
*
* @return string
*/
public function getUsername(): string
{
return $this->username;
}
/**View on GitHub (pinned to 31c1bbc10f)