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 Azure Laravel Package

socialiteproviders/microsoft-azure

Laravel Socialite provider for Microsoft Azure (Entra ID) OAuth login. Configure client ID, secret and tenant to sign users in, retrieve their id, name and email, support multiple tenants, and generate Azure logout URLs.

View on GitHub
Deep Wiki
Context7

Getting Started

Install the provider alongside Socialite and the manager package it depends on:

composer require socialiteproviders/microsoft-azure

Then three things must all be true before Socialite::driver('azure') works — missing any one of them produces a confusing error rather than an obvious one:

  1. Register an app in Microsoft Entra ID (Azure Portal → App registrations). Add a Web platform redirect URI that exactly matches your callback (e.g. https://app.test/auth/azure/callback) and create a client secret.
  2. Add the credentials to 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'),
],
  1. Bridge the provider into Socialite with the SocialiteWasCalled listener. On Laravel 11+ this goes in AppServiceProvider::boot():
use Illuminate\Support\Facades\Event;

Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) {
    $event->extendSocialite('azure', \SocialiteProviders\Azure\Provider::class);
});

On Laravel 10 and below, add the same listener class to $listen in app/Providers/EventServiceProvider.php.

Where to look first: the tenant value. It is not optional in practice — it is interpolated into both the authorize and token endpoints (login.microsoftonline.com/{tenant}/oauth2/v2.0/...). Use common for "any Microsoft account", organizations for work/school only, consumers for personal accounts only, or a tenant GUID for single-tenant apps.

First use case: an internal tool where you want to restrict sign-in to your own organisation. Point tenant at your directory GUID, add the routes, and you have SSO in an afternoon:

Route::get('/auth/azure/redirect', fn () => Socialite::driver('azure')->redirect());
Route::get('/auth/azure/callback', function () {
    $azureUser = Socialite::driver('azure')->user();
    // ['id' => oid, 'name' => displayName, 'email' => mail, 'nickname' => mailNickname]
});

Implementation Patterns

Persist the provider identity, not the email. The package is a thin HTTP wrapper — it does not create users, store tokens, or link accounts. Your callback is responsible for all of it. Store a provider + provider_user_id pair (the Azure id claim is the immutable oid) with a unique index, and resolve users through it:

$azureUser = Socialite::driver('azure')->user();

$account = SocialAccount::firstOrCreate(
    ['provider' => 'azure', 'provider_user_id' => $azureUser->getId()],
    ['email' => $azureUser->getEmail(), 'name' => $azureUser->getName()]
);

Auth::login($account->user);

Match on provider_user_id first and only fall back to email-linking for accounts you have already verified. Email local-parts get recycled inside tenants.

Scope requests deliberately. The default scope set covers openid profile email plus User.Read for the Graph call that populates the user object. When you need more (groups, directory, SharePoint), request it per-flow:

return Socialite::driver('azure')->scopes(['User.Read', 'GroupMember.Read.All'])->redirect();

Any scope beyond the basics requires admin consent on the app registration, otherwise the callback fails with AADSTS65001.

Store refresh tokens if you need Graph later. Add offline_access to the scopes and read the raw token response — Socialite's user() object discards it:

$socialite = Socialite::driver('azure');
$user  = $socialite->user();
$token = $socialite->getAccessTokenResponse($socialite->getAccessToken()); // has refresh_token

Encrypt these at rest and refresh proactively; the package will not do it for you.

Keep the tenant in sync with your data model. A user's oid is unique per tenant. If you later add a second tenant (e.g. after an acquisition), the same human arrives as a new provider_user_id. Treat tenant as part of the account key if you expect multi-tenant usage.

Testing. Point graph_url at a local mock server (WireMock, Http::fake-backed route, etc.) so the profile call is deterministic without touching Microsoft. Note the access token is sent to whatever URL is configured — see the gotchas below. Override per-test via config(['services.azure.graph_url' => 'http://127.0.0.1:8080/me']).

Gotchas and Tips

  • Driver [azure] is not supported means the event listener is missing or registered after Socialite resolves the driver. This is the single most common install failure, and it is silent in production if you only registered it in a service provider that is not loaded in console/queue contexts.
  • invalid_state on callback almost always means the session was lost between redirect and return. Usual culprits: SESSION_DOMAIN not covering both hosts, APP_URL differing from the actual host, HTTPS behind a proxy without TrustProxies configured, or a redirect URI that goes from app.test to www.app.test. Fix the root cause; ->stateless() disables CSRF state verification and should only be used for genuinely stateless API flows.
  • graph_url sends your access token there. The README warns about this for good reason — never point it at an untrusted host, and never let it be user-influenced (e.g. driven by a request header or a shared env var in a multi-tenant app). Pin it in config/services.php only.
  • mail is frequently null. Personal Microsoft accounts and some guest accounts have no mail claim. Fall back to userPrincipalName from $user->getRaw(), and decide explicitly whether a null email is a login failure or an account-creation prompt.
  • Client secret expiry. Entra ID secrets expire (max 24 months) and the failure surfaces as invalid_client on the token exchange, days after you have forgotten about it. Calendar it, or move to certificate credentials. Also remember secrets are only visible once — store them in your password manager, not a README.
  • Redirect URI must match exactly, including trailing slash, scheme, and port. http://localhost:8000/auth/azure/callback and http://127.0.0.1:8000/... are different registrations. Also make sure you registered it under Web, not Single-page application — the SPA platform does not accept a client secret.
  • Clear config after env changes. php artisan config:cache in deploy means a changed AZURE_TENANT_ID has no effect until config:clear. This has burned many people mid-debugging.
  • Wrap the callback in a try/catch. Users cancel consent (AADSTS65004), admins deny (AADSTS65001), and state validation throws Laravel\Socialite\Two\InvalidStateException. None of these should produce a 500 — redirect back to your login page with a flash message and log the exception.
  • proxy config exists for locked-down networks where outbound HTTPS must go through a forward proxy. It only applies to this provider's Guzzle client, not the rest of your app.
  • No logout support. Signing out of your app does not sign the user out of Microsoft. If you need true single logout, redirect to https://login.microsoftonline.com/{tenant}/oauth2/v2.0/logout?post_logout_redirect_uri=... yourself and register the post-logout URI in Entra ID.
  • Extension point: the provider is ~200 lines. If Microsoft changes a claim shape, subclass SocialiteProviders\Azure\Provider, override mapUserToObject(), and register your subclass in the extendSocialite listener instead of forking.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource