alancting/oauth2-microsoft-openid
Since this package is primarily designed for Symfony, Laravel developers will need to adapt it using knpuniversity/oauth2-client-bundle or php-league/oauth2-client directly. Here’s how to start:
Install Dependencies
composer require league/oauth2-client alancting/oauth2-microsoft-openid
Configure the OAuth Client Create a service provider to register the Microsoft provider (Laravel doesn’t natively support Symfony bundles). Example:
// app/Providers/AuthServiceProvider.php
use Alancting\OAuth2\OpenId\Client\Provider\AzureAdProvider; // or AdfsProvider
use League\OAuth2\Client\Provider\GenericProvider;
public function boot()
{
$this->registerMicrosoftProvider();
}
protected function registerMicrosoftProvider()
{
$provider = new AzureAdProvider([
'clientId' => env('AZURE_AD_CLIENT_ID'),
'clientSecret' => env('AZURE_AD_CLIENT_SECRET'),
'redirectUri' => env('AZURE_AD_REDIRECT_URI'),
'urlAuthorize' => 'https://login.microsoftonline.com/' . env('AZURE_AD_TENANT') . '/oauth2/v2.0/authorize',
'urlAccessToken' => 'https://login.microsoftonline.com/' . env('AZURE_AD_TENANT') . '/oauth2/v2.0/token',
'urlResourceOwnerDetails' => 'https://graph.microsoft.com/v1.0/me',
'scopes' => ['openid', 'profile', 'offline_access'],
]);
$this->app->singleton('oauth.microsoft', function () use ($provider) {
return new GenericProvider($provider->getBaseConfiguration());
});
}
Create a Login Route Use Laravel Socialite or manually handle the OAuth flow:
Route::get('/login/microsoft', function () {
$provider = app('oauth.microsoft');
$authUrl = $provider->getAuthorizationUrl();
return redirect()->to($authUrl);
});
Route::get('/login/microsoft/callback', function (Request $request) {
$provider = app('oauth.microsoft');
$token = $provider->getAccessToken('authorization_code', [
'code' => $request->query('code')
]);
$user = $provider->getResourceOwner($token);
// Store user data in session/database
});
Leverage the package’s built-in token handling for seamless integration:
getOAuthCredential() to fetch tokens (access, ID, refresh).
$credential = app('oauth.microsoft')->getOAuthCredential();
$accessToken = $credential->getAccessToken();
$idTokenJWT = $credential->getIdTokenJWT();
upn, name):
$payload = $idTokenJWT->getPayload();
$userEmail = $payload['upn'] ?? $payload['email'];
Request additional API permissions (e.g., Microsoft Graph) by extending scopes:
# .env
AZURE_AD_API_RESOURCE_1=https://graph.microsoft.com/.default
AZURE_AD_API_RESOURCE_2=https://your-api.com/.default
Configure in provider options:
$provider->setProviderOptions([
'other_resource_scopes' => [
env('AZURE_AD_API_RESOURCE_1'),
env('AZURE_AD_API_RESOURCE_2'),
],
]);
Redirect users to Microsoft’s logout endpoint:
$logoutUrl = app('oauth.microsoft')->getLogoutUrl();
return redirect()->to($logoutUrl);
Assign roles dynamically based on token claims:
$roles = ['ROLE_USER'];
if ($payload['roles'] ?? null) {
$roles = array_merge($roles, array_map(fn($role) => 'ROLE_' . strtoupper($role), $payload['roles']));
}
auth()->loginUsingId($userId, $roles);
Handle token expiration gracefully:
try {
$credential = $client->getOAuthCredential();
} catch (\League\OAuth2\Client\Provider\Exception\TemporaryCredentialsException $e) {
$refreshToken = $e->getRefreshToken();
$newCredential = $client->getOAuthCredential($refreshToken);
}
Deprecated Features:
Scope Configuration:
openid and profile scopes are always included for ID token validation.offline_access for refresh tokens.Token Validation:
iss (issuer) and aud (audience) claims in the ID token to prevent spoofing:
$issuer = 'https://login.microsoftonline.com/' . env('AZURE_AD_TENANT') . '/v2.0';
if ($payload['iss'] !== $issuer) {
throw new \RuntimeException('Invalid issuer');
}
ADFS vs. Azure AD:
unique_name as the default user_key. Customize via provider_options.upn (user principal name) by default. Override with user_key if needed.Enable League OAuth Debugging:
Add this to your .env to log OAuth requests:
LEAGUE_OAUTH_DEBUG=true
Token Inspection: Use jwt.io to decode ID tokens for manual validation.
Common Errors:
invalid_grant: Check redirect_uri matches registered URIs in Azure AD/ADFS.AADSTS70002: Invalid client secret or tenant ID.AADSTS50011: Missing openid scope.Custom User Provider:
Extend Alancting\OAuth2\OpenId\Client\User\UserProvider to map Microsoft claims to your user model:
class CustomUserProvider extends UserProvider
{
protected function getUserData($idTokenPayload)
{
return [
'id' => $idTokenPayload['oid'],
'email' => $idTokenPayload['upn'],
'name' => $idTokenPayload['name'],
];
}
}
Custom Authenticator: For Laravel, create a custom guard authenticator:
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
class MicrosoftAuthenticator extends AbstractOAuth2Authenticator
{
public function authenticate(Request $request)
{
try {
$provider = app('oauth.microsoft');
$token = $provider->getAccessToken('authorization_code', [
'code' => $request->query('code'),
]);
$user = $provider->getResourceOwner($token);
return $this->createUser($user, $token);
} catch (IdentityProviderException $e) {
return redirect()->route('login')->with('error', $e->getMessage());
}
}
}
Logout Event Listener:
Listen for Laravel’s Illuminate\Auth\Events\Logout to trigger Microsoft logout:
Event::listen(Logout::class, function () {
$logoutUrl = app('oauth.microsoft')->getLogoutUrl();
return redirect()->to($logoutUrl);
});
$select query parameter to fetch only required fields:
$graphUrl = 'https://graph.microsoft.com/v1.0/me?$select=displayName,mail';
$response = $client->get($graphUrl, [
'headers' => ['Authorization' => 'Bearer ' . $accessToken],
]);
How can I help you explore Laravel packages today?