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

  1. Inspect/log the actual response body from Microsoft to see what was returned instead of an access_token.
  2. 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.
  3. Check for proxies or SSL-inspection appliances rewriting the response, and allowlist the Microsoft endpoint.
  4. 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

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


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)