Microsoft/Azure AD Authentication Implementation for Websites
Microsoft OAuth via Azure Active Directory is standard for B2B apps, enterprise portals, and SaaS services targeting companies using Microsoft 365. Allows employees to login with corporate credentials without separate passwords.
Application Types
- Single-tenant—only users from one specific Microsoft tenant (company)
- Multi-tenant—users from any Azure AD organization
- Personal accounts—personal Microsoft/Outlook accounts
- Combination—organizations and personal accounts
For corporate integrations, choose single-tenant or multi-tenant. For consumer apps—personal + organizational accounts.
Registering Application in Azure
- portal.azure.com → Azure Active Directory → App registrations → New registration
- Specify Redirect URI:
https://example.com/auth/microsoft/callback - Select Supported account types (single/multi-tenant)
- After creation: save Application (client) ID and Directory (tenant) ID
- Certificates & secrets → New client secret → save value (visible only immediately)
-
API permissions → add:
openid,profile,email,User.Read
Laravel Socialite
composer require laravel/socialite socialiteproviders/microsoft-azure
// config/services.php
'azure' => [
'client_id' => env('AZURE_CLIENT_ID'),
'client_secret' => env('AZURE_CLIENT_SECRET'),
'redirect' => env('AZURE_REDIRECT_URI'),
'tenant' => env('AZURE_TENANT_ID', 'common'), // 'common' for multi-tenant
],
class MicrosoftAuthController extends Controller
{
public function redirect(): RedirectResponse
{
return Socialite::driver('azure')
->scopes(['openid', 'profile', 'email', 'User.Read'])
->redirect();
}
public function callback(): RedirectResponse
{
try {
$msUser = Socialite::driver('azure')->user();
} catch (\Exception $e) {
return redirect('/login')->withErrors(['microsoft' => 'Microsoft authorization error']);
}
$user = User::updateOrCreate(
['azure_id' => $msUser->getId()],
[
'name' => $msUser->getName(),
'email' => $msUser->getEmail(),
'email_verified_at' => now(),
'azure_tenant_id' => $msUser->user['tid'] ?? null,
]
);
Auth::login($user, remember: true);
return redirect()->intended('/dashboard');
}
}
Single-Tenant: Organization Restriction
For single-tenant, specify concrete Tenant ID instead of 'common':
// config/services.php
'azure' => [
'tenant' => env('AZURE_TENANT_ID'), // specific tenant
],
Azure will issue tokens only to users in that organization.
Multi-Tenant: Tenant Validation
For multi-tenant apps, validate user is from allowed organization:
public function callback(): RedirectResponse
{
$msUser = Socialite::driver('azure')->user();
$tenantId = $msUser->user['tid'] ?? null;
$allowedTenants = explode(',', config('services.azure.allowed_tenants', ''));
if ($allowedTenants && !in_array($tenantId, $allowedTenants)) {
return redirect('/login')->withErrors([
'microsoft' => 'Your organization does not have access'
]);
}
// ...
}
Getting Additional Data via MS Graph
$graphResponse = Http::withToken($msUser->token)
->get('https://graph.microsoft.com/v1.0/me', [
'$select' => 'id,displayName,mail,userPrincipalName,jobTitle,department,officeLocation',
]);
$profile = $graphResponse->json();
// $profile['jobTitle'] — job title
// $profile['department'] — department
// $profile['officeLocation']— office
Employee avatar:
$photoResponse = Http::withToken($msUser->token)
->get('https://graph.microsoft.com/v1.0/me/photo/$value');
if ($photoResponse->ok()) {
Storage::disk('public')->put("avatars/{$user->id}.jpg", $photoResponse->body());
}
Timeline
| Stage | Time |
|---|---|
| Azure registration + permissions | 0.5 day |
| OAuth callback + tenant ID storage | 1.5 days |
| MS Graph: additional data, avatar | 1 day |
| Tests with real tenant | 1 day |
Total: 4–5 days.







