Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Microsoft Laravel Package

socialiteproviders/microsoft

Microsoft provider for Laravel Socialite adds Microsoft (Azure AD) OAuth login to your Laravel app. Install via Composer, configure your client ID, secret, and tenant, then use Socialite::driver('microsoft') to authenticate users and retrieve profile data.

View on GitHub
Deep Wiki
Context7

Getting Started

socialiteproviders/microsoft is a thin Socialite driver: it supplies the Entra ID / Azure AD v2.0 OAuth endpoints and maps the Graph me payload into a Laravel\Socialite\Two\User. It does nothing on its own — it plugs into laravel/socialite.

Install and wire it up:

composer require socialiteproviders/microsoft
// config/services.php
'microsoft' => [
    'client_id'     => env('MICROSOFT_CLIENT_ID'),
    'client_secret' => env('MICROSOFT_CLIENT_SECRET'),
    'redirect'      => env('MICROSOFT_REDIRECT_URI'),
    'tenant'        => env('MICROSOFT_TENANT_ID', 'common'), // common | organizations | consumers | <tenant-guid>
],

Register the event listener. On Laravel 10 and earlier, add it to app/Providers/EventServiceProvider.php:

protected $listen = [
    \SocialiteProviders\Manager\SocialiteWasCalled::class => [
        \SocialiteProviders\Microsoft\MicrosoftExtendSocialite::class.'@handle',
    ],
];

On Laravel 11+ event discovery usually picks this up, but if Socialite::driver('microsoft') throws InvalidArgumentException: Driver [microsoft] is not supported, register it explicitly — see Gotchas.

Routes:

Route::get('/auth/microsoft/redirect', fn () => Socialite::driver('microsoft')->redirect());
Route::get('/auth/microsoft/callback', function () {
    $ms = Socialite::driver('microsoft')->user();
    // $ms->getId()       -> oid
    // $ms->getName()     -> displayName
    // $ms->getEmail()    -> mail ?? userPrincipalName
    // $ms->getRaw()      -> full Graph /me payload
});

Where to look first:

  1. The provider source (vendor/socialiteproviders/microsoft/MicrosoftProvider.php) — it's ~150 lines and tells you exactly which claims map to which User fields, and which scopes are requested by default.
  2. Your Entra app registration (Azure Portal → Entra ID → App registrations) — redirect URIs, supported account types, API permissions, client secret expiry.
  3. config/services.php and config/session.php — the two config files that cause most "it works locally" failures.

First use case: add a "Sign in with Microsoft" button next to your existing Google/GitHub buttons, route the callback into the same account-linking logic you already have, and key users on oid (not email).

Implementation Patterns

Minimal controller flow. The provider is stateless about your app; you own the user record. A typical callback:

public function callback()
{
    try {
        $ms = Socialite::driver('microsoft')->user();
    } catch (\Laravel\Socialite\Two\InvalidStateException $e) {
        return redirect('/login')->withErrors('Login session expired, please retry.');
    }

    $account = SocialAccount::firstOrCreate(
        ['provider' => 'microsoft', 'provider_user_id' => $ms->getId()],
        [
            'tenant_id' => $ms->getRaw()['tid'] ?? null,
            'email'     => $ms->getEmail(),
            'name'      => $ms->getName(),
        ]
    );

    Auth::login($account->user);
    return redirect()->intended('/dashboard');
}

Request extra scopes. Pass an array to scopes(); it replaces the driver defaults, so include the basics you still need:

Socialite::driver('microsoft')
    ->scopes(['openid', 'profile', 'email', 'offline_access', 'User.Read'])
    ->redirect();

offline_access is what gets you a refresh_token in $ms->accessTokenResponseBody. Without it you have a ~1 hour access token and nothing to refresh with.

Per-request tenant override. For B2B apps that want a "sign in with your work account" flow separate from "personal account", you can override the tenant per redirect using with() (exact key depends on your pinned version — check getTenant() in the provider source):

Socialite::driver('microsoft')
    ->with(['tenant' => 'organizations'])
    ->redirect();

Store tokens if you'll call Graph later. Socialite hands you the token but does not manage its lifecycle:

$token = $ms->token;                          // access token
$refresh = $ms->refreshToken;                 // if offline_access requested
$expires = $ms->expiresIn;                    // seconds

Encrypt at rest (encrypted cast), store expires_at, and build a small refresh helper. This is when "authentication only" quietly becomes "Graph integration" — plan for it up front.

Testing. Mock Socialite so tests never hit Microsoft:

Socialite::shouldReceive('driver->user')->andReturn(
    (new \Laravel\Socialite\Two\User)->map([
        'id' => '00000000-0000-0000-0000-000000000000',
        'name' => 'Test User',
        'email' => '[email protected]',
    ])
);

Combine with an integration test against a dedicated non-production Entra tenant for the real OAuth round-trip.

Feature-flag the rollout. Wrap the routes behind a flag and pilot with an internal group in a dev tenant before enabling the production tenant. This buys you a clean rollback without touching code.

Keep the adapter thin. Put the provider-specific mapping (claim selection, tenant handling) in one small service or SocialAccount factory. That's the seam you swap if you ever move to microsoft-azure, MSAL, or an in-house AbstractProvider.

Gotchas and Tips

  • oid is per-tenant, not global. The same person as a guest in two tenants has two oid values. Store oid + tid together and treat the pair as the identity key. Never key on email or userPrincipalName — UPNs change, guests often have a #EXT# UPN, and personal accounts may have no mail at all.
  • Driver not registered in Laravel 11+. If you get Driver [microsoft] is not supported, event discovery didn't find MicrosoftExtendSocialite. Either keep the listener in EventServiceProvider (works on all versions), or explicitly Event::listen(SocialiteWasCalled::class, MicrosoftExtendSocialite::class) in a service provider boot().
  • config:cache silently bakes credentials. After changing MICROSOFT_TENANT_ID, client_secret, or the redirect, run php artisan config:cache (or config:clear) in every environment. "It works locally but 500s in staging" is almost always this.
  • Redirect URI must match byte-for-byte. Trailing slash, http vs https, localhost vs 127.0.0.1, and port all count. A mismatch surfaces as AADSTS50011 at the Microsoft login page — the user never reaches your callback.
  • Consent errors after adding scopes. Adding Mail.Read or a Graph scope requires re-consent; without admin consent you'll see AADSTS65001 for regular users. Grant admin consent in the app registration, or design a consent-prompt fallback.
  • AADSTS700016 (app not in tenant). In single-tenant mode, a user from another tenant cannot sign in. If guests and B2B collaborators are in scope, use common or organizations and handle the tenant yourself.
  • Client secret expiry takes the whole login flow down. Set a calendar reminder or monitor the Graph token exchange response; a 401 from login.microsoftonline.com is the tell. Rotation is a config change plus config:cache.
  • getEmail() can be null. The provider falls back to userPrincipalName, but personal accounts often only have email in the id_token, not me. For anything identity-critical, read $user->getRaw()['mail'] ?? $user->getRaw()['userPrincipalName'] ?? $user->getId().
  • No avatar by default. Microsoft's /me doesn't return a photo; the provider leaves avatar empty. If you need one, call https://graph.microsoft.com/v1.0/me/photo/$value with the stored access token — and handle 404 for users with no photo.
  • State/session mismatch behind a load balancer. Socialite stores OAuth state in the session. If nodes don't share a session store, or your redirect and callback domains differ, you'll get InvalidStateException intermittently. Fix the session driver, not the provider.
  • ->scopes() replaces defaults. If you pass only ['User.Read'] you'll lose openid / profile / email and get a weird, sparse user object. Always list the full set you want.
  • Tenant value cheat sheet. common = work/school + personal; organizations = work/school only; consumers = personal only; a GUID = that tenant only. Pick deliberately — it determines who can log in, not just where they land.
  • Watch the microsoft vs microsoft-azure split. Both exist in the SocialiteProviders ecosystem with different config keys and claim mapping. Pick one and stay there; mixing config blocks is a common source of "why is my id null".
  • Stale split-package version. Releases are cut from the socialiteproviders/providers monorepo. Pin the package and verify Composer constraints against your installed laravel/socialite and socialiteproviders/manager when you upgrade either. If the package stalls on a Socialite major, the driver is small enough to fork into an in-house AbstractProvider subclass — budget a day for that, not a week.
  • Conditional access / MFA are not errors in your code. When a user fails MFA or hits a CA policy, they surface at Microsoft, not in your logs. Log the callback hit and the AADSTS code from the query string (?error=...&error_description=...) so support can triage.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings