Installation Add the package via Composer:
composer require microsoft/microsoft-graph
Requires PHP 8.2+.
Register an Azure AD App
authorization_code flow) and API permissions (e.g., User.Read, Mail.ReadWrite).tenantId, clientId, and clientSecret (or certificate).First Request (Client Credentials Flow)
use Microsoft\Graph\GraphServiceClient;
use Microsoft\Kiota\Authentication\Oauth\ClientCredentialContext;
$tokenContext = new ClientCredentialContext('tenantId', 'clientId', 'clientSecret');
$client = new GraphServiceClient($tokenContext);
// Fetch a user (application permissions)
$user = $client->users('user@domain.com')->get()->wait();
echo $user->getDisplayName();
First Request (User Delegation)
Redirect users to Microsoft’s OAuth endpoint (e.g., /authorize?...), then exchange the authCode for a token:
$tokenContext = new AuthorizationCodeContext('tenantId', 'clientId', 'clientSecret', $authCode, 'redirectUri');
$client = new GraphServiceClient($tokenContext, ['User.Read']);
$user = $client->me()->get()->wait();
$client = new GraphServiceClient(
new ClientCredentialContext('tenantId', 'clientId', 'clientSecret')
);
// After redirect from Microsoft's OAuth endpoint:
$tokenContext = new AuthorizationCodeContext(
'tenantId', 'clientId', 'clientSecret', $_GET['code'], 'https://your-app.com/callback'
);
$client = new GraphServiceClient($tokenContext, ['User.Read']);
$tokenContext = new OnBehalfOfContext('tenantId', 'clientId', 'clientSecret', $frontendToken);
$client = new GraphServiceClient($tokenContext, ['Mail.Read']);
Promise objects. Use .wait() to block or handle callbacks:
$user = $client->users()->post([
'displayName' => 'John Doe',
'mail' => 'john@example.com',
'accountEnabled' => true
])->wait();
.get() with top/skip or iterate over nextLink:
$users = $client->users()->get()->wait();
while ($users->getODataNextLink()) {
$users = $client->users()->getNextPage($users->getODataNextLink())->wait();
}
Combine multiple requests into a single call:
$requests = [
new GraphRequest('/users', 'GET'),
new GraphRequest('/me/messages', 'GET', ['$top' => 10]),
];
$responses = $client->users()->sendBatchRequest($requests)->wait();
Wrap calls in try-catch for ApiException:
try {
$user = $client->users('nonexistent@domain.com')->get()->wait();
} catch (ApiException $e) {
$error = $e->getError();
log("Error: {$error->getMessage()}, Code: {$error->getCode()}");
}
Override Guzzle defaults (e.g., for proxies or timeouts):
use Microsoft\Graph\Core\GraphClientFactory;
$httpClient = GraphClientFactory::createWithConfig([
'timeout' => 30,
'proxy' => 'http://proxy.example.com',
]);
$client = GraphServiceClient::createWithRequestAdapter(
new GraphRequestAdapter(
new GraphPhpLeagueAuthenticationProvider($tokenContext),
$httpClient
)
);
Target China/US Gov clouds:
$client = new GraphServiceClient($tokenContext, [], NationalCloud::CHINA);
Token Expiry and Refresh
$tokenProvider = new GraphPhpLeagueAccessTokenProvider($tokenContext);
$token = $tokenProvider->getAccessToken()->wait();
ClientCredentialContext tokens are cached by {tenantId}-{clientId}. Reuse the same $tokenContext instance for consistent caching.Async vs. Sync Confusion
.wait() leads to silent failures. Use ->wait() or then() for synchronous/asynchronous handling:
// Async with callback
$client->me()->get()->then(
fn($user) => echo $user->getDisplayName(),
fn($ex) => error_log($ex->getMessage())
);
Scope Mismatches
$client = new GraphServiceClient($tokenContext, ['User.ReadWrite']); // Must match Azure AD permissions!
Redirect URI Mismatch
authorization_code flow, the redirectUri in the context must match the registered URI in Azure AD (case-sensitive).Rate Limiting
use Microsoft\Kiota\Abstractions\ApiException;
use GuzzleHttp\Exception\RequestException;
try {
$response = $client->me()->get()->wait();
} catch (ApiException $e) {
if ($e->getStatusCode() === 429) {
sleep($e->getRetryAfter());
retry();
}
}
Model Serialization
User) require proper serialization. Use ->toJson() or ->getProperty():
$user = $client->me()->get()->wait();
$email = $user->getMail(); // Direct property access
Enable HTTP Logging Configure Guzzle to log requests/responses:
$httpClient = GraphClientFactory::createWithConfig([
'handler' => HandlerStack::create([
new \GuzzleHttp\Middleware::tap(
fn($request, $options) => error_log($request->getUri())
),
]),
]);
Inspect Tokens Dump the cached token for debugging:
$tokenProvider = new GraphPhpLeagueAccessTokenProvider($tokenContext);
$token = $tokenProvider->getAccessToken()->wait();
error_log($token->getToken());
Validate Azure AD Permissions
AccessTokenCache for persistent storage (e.g., Redis):
use Microsoft\Kiota\Authentication\Cache\AccessTokenCache;
class RedisTokenCache implements AccessTokenCache {
public function get($key): ?AccessToken { /* ... */ }
public function set($key, AccessToken $token): void { /* ... */ }
}
$client = GraphServiceClient::createWithAuthenticationProvider(
GraphPhpLeagueAuthenticationProvider::createWithAccessTokenProvider
How can I help you explore Laravel packages today?