scopes(['openid', 'profile', 'email'])->redirect(); } public function callback(Request $request): RedirectResponse { // Entra renvoie ?error=… en cas de refus ou de mauvaise configuration : on l'affiche au lieu d'une 500. if ($request->filled('error')) { Log::warning('SSO Entra refusé', ['error' => $request->query('error'), 'description' => $request->query('error_description')]); abort(403, 'Connexion Entra refusée : '.Str::limit((string) $request->query('error_description'), 300)); } try { $entra = Socialite::driver('azure')->user(); } catch (InvalidStateException) { // Session perdue entre /login et le retour Entra : cookie expiré, ou connexion lancée // sur un autre nom d'hôte (127.0.0.1 au lieu de localhost). On relance une fois, // depuis l'hôte du callback, ce qui suffit dans ces deux cas. if (! $request->session()->pull('sso_retry')) { $request->session()->put('sso_retry', true); return redirect()->route('login'); } abort(419, 'Session de connexion expirée. Fermez l\'onglet et ouvrez de nouveau '.config('app.url').'/login'); } $request->session()->forget('sso_retry'); $tenant = Tenant::where('entra_tenant_id', config('signature.default_tenant.entra_tenant_id'))->firstOrFail(); $directoryUser = DirectoryUser::where('tenant_id', $tenant->id)->where('entra_oid', $entra->getId())->first(); abort_unless($directoryUser && $directoryUser->account_enabled, 403, 'Compte inconnu de l\'annuaire synchronisé.'); $user = User::updateOrCreate( ['entra_oid' => $entra->getId()], [ 'tenant_id' => $tenant->id, 'name' => $directoryUser->display_name ?? $entra->getName(), 'email' => $directoryUser->mail ?? $entra->getEmail(), 'role' => $this->roleFor($directoryUser), ], ); Auth::login($user); $request->session()->regenerate(); AuditLog::record('admin', $user->email, 'login', null, ['role' => $user->role], $tenant->id); return redirect()->intended($user->role ? route('admin.dashboard') : route('portal.home')); } public function logout(Request $request): RedirectResponse { Auth::logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return redirect('/'); } /** Rôle le plus élevé parmi les groupes AD de rôle dont l'agent est membre (recalculé à chaque connexion). */ private function roleFor(DirectoryUser $user): ?string { $groups = $user->groups()->pluck('display_name')->map(fn ($n) => strtolower($n))->all(); $role = null; foreach (config('signature.roles') as $name => $group) { if (in_array(strtolower($group), $groups, true) && (User::ROLE_RANK[$name] ?? 0) > (User::ROLE_RANK[$role] ?? 0)) { $role = $name; } } return $role; } }