passbolt/passbolt_api · error · InternalErrorException
Failed to obtain SMTP OAuth2 access token.
Error message
Failed to obtain SMTP OAuth2 access token.
What it means
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.
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).
Example fix
// before: secret expired, AADSTS7000215
throw new InternalErrorException(__('Failed to obtain SMTP OAuth2 access token.'));
// after: rotate the secret in Azure Portal, update settings, retry
// Azure Portal > App registrations > Certificates & secrets > New client secret
// then re-save smtp settings with the new secret Defensive patterns
Strategy: try-catch
Validate before calling
// preflight: verify credentials can reach Microsoft before configuring
$resp = $http->post("https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token", [
'grant_type' => 'client_credentials', 'client_id' => $clientId,
'client_secret' => $secret, 'scope' => 'https://outlook.office365.com/.default',
]);
if (!$resp->isOk()) { /* fix config before wiring into passbolt */ } Try / catch
try {
$token = $service->getAccessToken();
} catch (InternalErrorException $e) {
// read application logs for the AADSTS error_description and fix Azure config
Log::error('OAuth token exchange failed; check Azure secret/consent.');
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Client ID should be a valid UUID.
- SMTP OAuth2 token response from Microsoft did not contain…
- Tenant ID should be a valid UUID.
- Cannot parse JWKS endpoint response.
- Cannot parse JWKS endpoint response.
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/5c89de929b2d7f63.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltCe/SmtpSettings/src/Service/SmtpOauthExchangeOnlineService.php:124
* @throws \Cake\Http\Exception\InternalErrorException If the token request fails.
* @see https://learn.microsoft.com/en-us/entra/identity-platform/v2-oauth2-client-creds-grant-flow#get-a-token
*/
public function getAccessToken(): string
{
$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(): stringView on GitHub (pinned to 31c1bbc10f)