untt/oauth2-microsoft

Microsoft OAuth2 Provider for The PHP League OAuth2 Client

Maintainers

Package info

github.com/tunterreitmeier/oauth2-microsoft

pkg:composer/untt/oauth2-microsoft

Statistics

Installs: 70

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

0.0.3 2026-02-27 02:40 UTC

This package is auto-updated.

Last update: 2026-03-29 15:00:02 UTC


README

CI Latest Stable Version License

A production-ready Microsoft OAuth2 provider package for thephpleague/oauth2-client, enabling authentication via Microsoft Entra (Azure AD) with support for both personal and organizational accounts.

Features

  • Microsoft Graph API integration: Fetches comprehensive user data from Microsoft Graph
  • Flexible tenant support: Common, organizations-only, consumers-only, or specific tenant
  • OpenID Connect support: Helper methods to access ID token claims

Requirements

  • PHP 8.2 or higher
  • league/oauth2-client ^2.6.0

Installation

composer require untt/oauth2-microsoft

Usage

Basic Usage

Default tenant is common which will work for both personal and work accounts.

use Unt\OAuth2\Client\Provider\MicrosoftProvider;

$provider = new MicrosoftProvider([
    'clientId'     => '{microsoft-client-id}',
    'clientSecret' => '{microsoft-client-secret}',
    'redirectUri'  => 'https://example.com/callback',
]);

// Get authorization URL
$authorizationUrl = $provider->getAuthorizationUrl();

// Save state for CSRF protection
$_SESSION['oauth2state'] = $provider->getState();

// Redirect user to authorization URL
header('Location: ' . $authorizationUrl);
exit;

Handle Callback

// Verify state for CSRF protection
if (empty($_GET['state']) || ($_GET['state'] !== $_SESSION['oauth2state'])) {
    unset($_SESSION['oauth2state']);
    exit('Invalid state');
}

try {
    // Get access token
    $token = $provider->getAccessToken('authorization_code', [
        'code' => $_GET['code']
    ]);

    // Get user details from Microsoft Graph API
    $resourceOwner = $provider->getResourceOwner($token);

    echo 'Hello, ' . $resourceOwner->getDisplayName() . '!';
    echo 'Email: ' . $resourceOwner->getEmail();
    echo 'User ID: ' . $resourceOwner->getId();

} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
    exit('Authentication failed: ' . $e->getMessage());
}

Tenant Selection

Organizations Only (Work/School Accounts)

use Unt\OAuth2\Client\Provider\MicrosoftProvider;

$provider = new MicrosoftProvider([
    'clientId'     => '{microsoft-client-id}',
    'clientSecret' => '{microsoft-client-secret}',
    'redirectUri'  => 'https://example.com/callback',
    'tenant'       => MicrosoftProvider::TENANT_ORGANIZATIONS,
]);

Specific Tenant

$provider = new MicrosoftProvider([
    'clientId'     => '{microsoft-client-id}',
    'clientSecret' => '{microsoft-client-secret}',
    'redirectUri'  => 'https://example.com/callback',
    'tenant'       => '12345678-1234-1234-1234-123456789012', // Your tenant ID
]);

Accessing ID Token Claims

If you need access to the OpenID Connect ID token claims (e.g., tenant ID, authentication metadata), you can add them to the scopes.

// Request OpenID Connect scopes when getting authorization URL
use new \Unt\OAuth2\Client\Provider\MicrosoftProvider;

$authorizationUrl = $provider->getAuthorizationUrl([
    'scope' => array_merge(
        ['openid', 'profile', 'email']
        ['User.Read']
    )
]);

// or use helper method
$provider = (new MicrosoftProvider())->requireOpenIdScopes();

// After getting the access token
$token = $provider->getAccessToken('authorization_code', ['code' => $_GET['code']]);

// Decode ID token claims
$idToken = $provider->getIdTokenClaims($token);

echo 'Tenant ID: ' . $idToken->tenantId;
echo 'Name: ' . $idToken->name;
echo 'Username: ' . $idToken->preferredUsername;
echo 'Email: ' . $idToken->email;

// full token payload: $idToken->fullPayload

Please note that the OpenID Connect JWT is not actively verified. Just as with the Access Token, you should verify the state to detect forged requests.

Additional Scopes

Request additional Microsoft Graph API permissions:

$authorizationUrl = $provider->getAuthorizationUrl([
    'scope' => ['openid', 'User.Read', 'Calendars.Read', 'Mail.Read']
]);

Please note that Microsoft usually does not allow scopes across different 'product spaces'. So if you require for example User.Read from https://graph.microsoft.com, you will not be able to request SMTP.Send from https://outlook.office.com in the same token. You will have to request these individually, using a refresh token.

In Microsoft identity platform authorization, token, or consent requests, omitting the resource identifier in the scope parameter defaults to Microsoft Graph. For example, User.Read is treated as https://graph.microsoft.com/User.Read.

Refresh Tokens

To get a refresh token, include the offline_access scope:

$authorizationUrl = $provider->getAuthorizationUrl([
    'scope' => ['openid', 'User.Read', 'offline_access']
]);

// Store the refresh token
$token->getRefreshToken();

// Later, refresh the token
if ($token->hasExpired()) {
    $newToken = $provider->getAccessToken('refresh_token', [
        'refresh_token' => $token->getRefreshToken()
    ]);
    
    // store the new refresh token, in case it also has expired
} 

Resource Owner Methods

The MicrosoftResourceOwner object fetched from Microsoft Graph API provides:

Method Description Example Value
getId() User unique identifier "12345678-abcd-..."
getUserPrincipalName() Email-like identifier "user@company.com"
getDisplayName() Full name "John Doe"
getGivenName() First name "John"
getSurname() Last name "Doe"
getEmail() Email address * "john@company.com"
getJobTitle() Job title "Software Engineer"
getOfficeLocation() Office location "Building 42"
getMobilePhone() Mobile phone "+1234567890"
getBusinessPhones() List of phone numbers ["+1234567890"]
getPreferredLanguage() Locale preference "en-US"
toArray() All data as array array(...)

Testing

Run the test suite:

composer test           # Run PHPUnit tests
composer test-coverage  # Run tests with HTML coverage report
composer check:static   # Run static analysis (PHPStan level 8)
composer check:style    # Check code style (PSR-12)
composer fix:style      # Fix code style issues
composer check          # Run all checks (style, PHPStan, tests)

Contributing

Contributions are welcome! Please fork and create a PR.

License

GNU General Public License version 3 License. See LICENSE for details.

Resources