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

Oauth2 Azure Bundle Laravel Package

m4n50n/oauth2-azure-bundle

A Symfony bundle that simplifies Azure AD OAuth2 authentication by integrating the League OAuth2 Azure provider, letting you add Microsoft sign-in and protect routes with minimal configuration.

View on GitHub
Deep Wiki
Context7

Getting Started

Read this first: it's a Symfony bundle, not a Laravel package. There is no service provider, no config/oauth2azure.php, and nothing to php artisan vendor:publish. In a Laravel app you will not install m4n50n/oauth2-azure-bundle; you install what it wraps:

composer require thenetworg/oauth2-azure

That library is a league/oauth2-client provider for Microsoft Entra ID (Azure AD). The bundle is a ~2-star, zero-dependent convenience layer that registers that provider in Symfony's DI container. Treat it as reference documentation for configuration shape, not as a dependency.

Minimum viable setup in Laravel:

  1. config/services.php — add an azure block with client_id, client_secret, redirect, tenant.
  2. A singleton binding in AppServiceProvider so you get one configured Azure provider instance.
  3. Two routes: GET /auth/azure/redirect and GET /auth/azure/callback.
// AppServiceProvider::register()
$this->app->singleton(\TheNetwOrg\OAuth2\Client\Provider\Azure::class, fn () => new \TheNetwOrg\OAuth2\Client\Provider\Azure([
    'clientId'     => config('services.azure.client_id'),
    'clientSecret' => config('services.azure.client_secret'),
    'redirectUri'  => config('services.azure.redirect'),
    'tenant'       => config('services.azure.tenant', 'common'),
    'scopes'       => ['openid', 'profile', 'email', 'offline_access', 'User.Read'],
]));

Where to look first in the bundle repo: DependencyInjection/Configuration.php and Resources/config/services.*. That config tree is the only genuinely useful artifact — it enumerates every option the underlying provider accepts (tenant, scopes, resource, apiVersion, proxy, timeout, verify) and maps them to constructor keys. Copy that list into your services.php and you've extracted 100% of the bundle's value.

First real use case: internal-staff SSO. Restrict tenant to your directory GUID, request openid profile email offline_access, and you get corporate logins with zero password handling. That's the 20-minute win.

Implementation Patterns

Plain league/oauth2-client flow (recommended for full control). This is what the bundle does internally, and it maps cleanly onto two Laravel routes:

// redirect
public function redirect(Azure $azure) {
    $url = $azure->getAuthorizationUrl(['state' => Str::random(40)]);
    session(['azure_state' => $azure->getState(), 'azure_pkce' => $azure->getPkceCode()]);
    return redirect($url);
}

// callback
public function callback(Request $request, Azure $azure) {
    abort_unless($request->state === session('azure_state'), 403);
    $azure->setPkceCode(session('azure_pkce'));
    $token = $azure->getAccessToken('authorization_code', ['code' => $request->code]);
    $owner = $azure->getResourceOwner($token);

    $user = User::updateOrCreate(
        ['azure_oid' => $owner->getId()],
        ['name' => $owner->getFirstName().' '.$owner->getLastName(),
         'email' => $owner->getEmail(), 'tenant_id' => $owner->getTenantId()]
    );
    Auth::login($user);
    return redirect('/dashboard');
}

Key the local user row on the object id (oid), not email and not UPN. Email changes on marriage/renaming; oid is stable per directory.

Socialite bridge. If the rest of your app is Socialite-driven, don't hand-roll the flow — use Socialite::buildProvider(Azure::class, config('services.azure')). You keep Socialite::driver() ergonomics and the same underlying provider, and ->stateless() works for API/token-based callers.

Multi-tenant variants. The bundle's pattern is worth stealing here: rather than one global provider, register named instances keyed by tenant.

$this->app->bind('azure.tenant', fn ($app, $args) => new Azure([...$base, 'tenant' => $args['tenant']]));

Useful for a customer-facing app where you support both contoso.onmicrosoft.com guests and your own workforce directory, or for CI/service-to-service tokens.

Client credentials for daemon jobs. Same provider, different grant:

$token = $azure->getAccessToken('client_credentials', ['scope' => 'https://graph.microsoft.com/.default']);

Group-based authorization. AzureResourceOwner::getGroups() returns group GUIDs. Resolve them to names once (cache a GUID→name map from Graph), then Gate::define('finance', fn ($u) => in_array(config('azure.finance_group'), $u->azure_groups)). Don't hardcode display names in code.

Gotchas and Tips

  • tenant => 'common' breaks client credentials. The common/organizations aliases only work for user-delegated flows. Daemon apps must use an explicit tenant ID or domain — otherwise you get AADSTS50194.
  • v1 vs v2 confusion is the #1 support ticket. v1 uses resource=https://graph.microsoft.com; v2 uses scope=.../.default. Mixing them yields AADSTS70011 (invalid scope). Pick apiVersion => '2.0' and stay there; the bundle's config exposes the switch, which is exactly the trap — it invites you to leave it unset.
  • Redirect URI must match byte-for-byte, including trailing slash and http vs https. http://localhost:8000/auth/azure/callback in .env will not match http://127.0.0.1:8000/... in the portal. This is the most common "it works in staging, not locally" bug.
  • offline_access is required for a refresh token — and Entra rotates refresh tokens, so always persist the newly returned one. A silent token refresh loop with a stale RT fails after ~90 days.
  • Config key casing. The bundle's Symfony config uses snake_case; the provider constructor uses camelCase (clientId, redirectUri). If you port config straight across you'll get a provider with null credentials and a confusing AADSTS900144 at the token endpoint, not at construction. Validate eagerly in your singleton factory.
  • Groups claim overage. Users in >200 groups (or >6KB of claims) get _claim_names instead of groups. Detect it and fall back to POST https://graph.microsoft.com/v1.0/me/getMemberObjects with User.Read + GroupMember.Read.All. Silent empty getGroups() is not "user has no groups."
  • Debug with the correlation ID, not the message. Every AADSTS##### response carries trace_id and correlation_id from login.microsoftonline.com; log them. Decode the id_token at jwt.ms (never in prod with real tokens) to see what claims actually arrived. GET https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration tells you the exact endpoints and whether your tenant is configured as expected.
  • PKCE + state. The provider supports PKCE but won't store it for you — you must round-trip getPkceCode() through the session. Forgetting it, or losing state on a non-sticky-session load balancer, produces AADSTS501481/invalid-state errors at 3am.
  • Token skew. Id-token nbf/exp validation fails on containers with drifted clocks. Run NTP, or widen leeway via the provider's timeout/HTTP client options rather than disabling validation.
  • Bus factor. Two stars, no dependents, one maintainer, last release Feb 2024. Do not introduce the bundle into any non-Symfony codebase, and do not fork it either — pin thenetworg/oauth2-azure to a minor range, vendor the config array from Configuration.php, and keep an eye on Microsoft identity platform changes (Entra renamed endpoints in 2023; the library has trailed slightly).
  • Extension point if you need more: subclass Azure and override getResourceOwnerDetailsUrl() / createResourceOwner() to attach Graph data to the resource owner in one round trip, rather than issuing a second HTTP call in your controller. That's ~15 lines and saves latency on every login.
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.
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console