implement single login system
This commit is contained in:
parent
65e153153f
commit
4f12285e7a
@ -48,12 +48,13 @@ public function launchSSO($id)
|
|||||||
? 'organizations'
|
? 'organizations'
|
||||||
: 'consumers';
|
: 'consumers';
|
||||||
|
|
||||||
// Redirect to Microsoft OAuth authorize page with login_hint, domain_hint and custom callback
|
// Redirect to Microsoft OAuth authorize page with login_hint, domain_hint, prompt=none and custom callback
|
||||||
return Socialite::driver('microsoft')
|
return Socialite::driver('microsoft')
|
||||||
->redirectUrl(route('sso.callback'))
|
->redirectUrl(route('sso.callback'))
|
||||||
->with([
|
->with([
|
||||||
'login_hint' => $user->email,
|
'login_hint' => $user->email,
|
||||||
'domain_hint' => $domainHint
|
'domain_hint' => $domainHint,
|
||||||
|
'prompt' => 'none'
|
||||||
])
|
])
|
||||||
->redirect();
|
->redirect();
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
@ -74,6 +75,14 @@ public function launchSSO($id)
|
|||||||
*/
|
*/
|
||||||
public function handleSSOCallback(Request $request)
|
public function handleSSOCallback(Request $request)
|
||||||
{
|
{
|
||||||
|
// If Microsoft returns an error (user logged out or session expired)
|
||||||
|
if ($request->has('error')) {
|
||||||
|
Auth::logout();
|
||||||
|
$request->session()->invalidate();
|
||||||
|
$request->session()->regenerateToken();
|
||||||
|
return redirect()->route('login')->with('error', 'You have been signed out because your Microsoft session is no longer active.');
|
||||||
|
}
|
||||||
|
|
||||||
$user = Auth::user();
|
$user = Auth::user();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -87,12 +96,20 @@ public function handleSSOCallback(Request $request)
|
|||||||
'microsoft_token' => $microsoftUser->token,
|
'microsoft_token' => $microsoftUser->token,
|
||||||
'microsoft_refresh_token' => $microsoftUser->refreshToken ?? $user->microsoft_refresh_token,
|
'microsoft_refresh_token' => $microsoftUser->refreshToken ?? $user->microsoft_refresh_token,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Update verification timestamp
|
||||||
|
session(['ms_session_last_checked' => now()]);
|
||||||
}
|
}
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
\Illuminate\Support\Facades\Log::error('Microsoft SSO callback failed', [
|
\Illuminate\Support\Facades\Log::error('Microsoft SSO callback failed', [
|
||||||
'message' => $e->getMessage()
|
'message' => $e->getMessage()
|
||||||
]);
|
]);
|
||||||
// If callback fails but we have target URL, we can still try to redirect
|
|
||||||
|
// Treat token exchange failure as an expired session
|
||||||
|
Auth::logout();
|
||||||
|
$request->session()->invalidate();
|
||||||
|
$request->session()->regenerateToken();
|
||||||
|
return redirect()->route('login')->with('error', 'Your Microsoft session has expired. Please log in again.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$targetUrl = session('sso_target_url', 'https://outlook.office.com/mail/');
|
$targetUrl = session('sso_target_url', 'https://outlook.office.com/mail/');
|
||||||
@ -110,6 +127,53 @@ public function handleSSOCallback(Request $request)
|
|||||||
return redirect($targetUrl);
|
return redirect($targetUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle silent session verification callback from Microsoft.
|
||||||
|
*/
|
||||||
|
public function handleSessionCheckCallback(Request $request)
|
||||||
|
{
|
||||||
|
// If Microsoft returns an error (user logged out or session expired)
|
||||||
|
if ($request->has('error')) {
|
||||||
|
Auth::logout();
|
||||||
|
$request->session()->invalidate();
|
||||||
|
$request->session()->regenerateToken();
|
||||||
|
return redirect()->route('login')->with('error', 'You have been signed out because you signed out of your Microsoft account.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$microsoftUser = Socialite::driver('microsoft')
|
||||||
|
->redirectUrl(route('sso.callback-check'))
|
||||||
|
->user();
|
||||||
|
|
||||||
|
$user = Auth::user();
|
||||||
|
if ($microsoftUser && $user) {
|
||||||
|
// Update active tokens in DB
|
||||||
|
$user->update([
|
||||||
|
'microsoft_token' => $microsoftUser->token,
|
||||||
|
'microsoft_refresh_token' => $microsoftUser->refreshToken ?? $user->microsoft_refresh_token,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Update verification timestamp
|
||||||
|
session(['ms_session_last_checked' => now()]);
|
||||||
|
}
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Illuminate\Support\Facades\Log::error('Microsoft session check callback failed', [
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Treat token exchange failure as an expired session
|
||||||
|
Auth::logout();
|
||||||
|
$request->session()->invalidate();
|
||||||
|
$request->session()->regenerateToken();
|
||||||
|
return redirect()->route('login')->with('error', 'Your Microsoft session has expired. Please log in again.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect back to the page they were trying to access
|
||||||
|
$origin = session('sso_check_origin', route('dashboard'));
|
||||||
|
session()->forget('sso_check_origin');
|
||||||
|
return redirect($origin);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the desktop application launcher page with web fallback.
|
* Render the desktop application launcher page with web fallback.
|
||||||
*/
|
*/
|
||||||
|
|||||||
57
app/Http/Middleware/VerifyMicrosoftSession.php
Normal file
57
app/Http/Middleware/VerifyMicrosoftSession.php
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Middleware;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Laravel\Socialite\Facades\Socialite;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class VerifyMicrosoftSession
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Handle an incoming request.
|
||||||
|
*/
|
||||||
|
public function handle(Request $request, Closure $next): Response
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
|
||||||
|
// Only check if user is authenticated and has a Microsoft ID linked
|
||||||
|
if ($user && $user->microsoft_id) {
|
||||||
|
$lastChecked = session('ms_session_last_checked');
|
||||||
|
|
||||||
|
// Check every 30 seconds upon page access/refresh
|
||||||
|
if (!$lastChecked || now()->diffInSeconds($lastChecked) > 30) {
|
||||||
|
// Set timestamp immediately to prevent redirect loop
|
||||||
|
session(['ms_session_last_checked' => now()]);
|
||||||
|
|
||||||
|
// Store target URL to return to after silent verification
|
||||||
|
session(['sso_check_origin' => $request->fullUrl()]);
|
||||||
|
|
||||||
|
// Determine domain hint based on user's email domain
|
||||||
|
$domainHint = (str_ends_with($user->email, '.onmicrosoft.com') || (str_contains($user->email, '@') && !str_ends_with($user->email, 'outlook.com') && !str_ends_with($user->email, 'hotmail.com') && !str_ends_with($user->email, 'live.com')))
|
||||||
|
? 'organizations'
|
||||||
|
: 'consumers';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Redirect to Microsoft with prompt=none to check session status
|
||||||
|
return Socialite::driver('microsoft')
|
||||||
|
->redirectUrl(route('sso.callback-check'))
|
||||||
|
->with([
|
||||||
|
'login_hint' => $user->email,
|
||||||
|
'domain_hint' => $domainHint,
|
||||||
|
'prompt' => 'none'
|
||||||
|
])
|
||||||
|
->redirect();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
\Illuminate\Support\Facades\Log::error('Microsoft session silent verification failed', [
|
||||||
|
'message' => $e->getMessage()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -14,6 +14,7 @@
|
|||||||
->withMiddleware(function (Middleware $middleware): void {
|
->withMiddleware(function (Middleware $middleware): void {
|
||||||
$middleware->alias([
|
$middleware->alias([
|
||||||
'role' => \App\Http\Middleware\EnsureRole::class,
|
'role' => \App\Http\Middleware\EnsureRole::class,
|
||||||
|
'verify-ms-session' => \App\Http\Middleware\VerifyMicrosoftSession::class,
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
->withExceptions(function (Exceptions $exceptions): void {
|
->withExceptions(function (Exceptions $exceptions): void {
|
||||||
|
|||||||
BIN
public/Screenshot_5.png
Normal file
BIN
public/Screenshot_5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 123 KiB |
BIN
public/Screenshot_6.png
Normal file
BIN
public/Screenshot_6.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
@ -19,16 +19,23 @@
|
|||||||
Route::get('/auth/microsoft/logout', [LoginController::class, 'frontChannelLogout'])->name('auth.microsoft.logout');
|
Route::get('/auth/microsoft/logout', [LoginController::class, 'frontChannelLogout'])->name('auth.microsoft.logout');
|
||||||
|
|
||||||
// User Dashboard Portal (requires standard user role or admin access)
|
// User Dashboard Portal (requires standard user role or admin access)
|
||||||
Route::middleware(['auth', 'role:user'])->group(function () {
|
Route::middleware(['auth', 'role:user', 'verify-ms-session'])->group(function () {
|
||||||
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
|
Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
|
||||||
|
});
|
||||||
|
|
||||||
|
Route::middleware(['auth', 'role:user'])->group(function () {
|
||||||
Route::get('/sso/launch/{id}', [DashboardController::class, 'launchSSO'])->name('sso.launch');
|
Route::get('/sso/launch/{id}', [DashboardController::class, 'launchSSO'])->name('sso.launch');
|
||||||
Route::get('/sso/callback', [DashboardController::class, 'handleSSOCallback'])->name('sso.callback');
|
Route::get('/sso/callback', [DashboardController::class, 'handleSSOCallback'])->name('sso.callback');
|
||||||
Route::get('/sso/launch-app', [DashboardController::class, 'launchApp'])->name('sso.launch-app');
|
Route::get('/sso/launch-app', [DashboardController::class, 'launchApp'])->name('sso.launch-app');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Route::middleware('auth')->group(function () {
|
||||||
|
Route::get('/sso/check-session-callback', [DashboardController::class, 'handleSessionCheckCallback'])->name('sso.callback-check');
|
||||||
|
});
|
||||||
|
|
||||||
// Admin Control Panel (requires admin role)
|
// Admin Control Panel (requires admin role)
|
||||||
Route::middleware(['auth', 'role:admin'])->group(function () {
|
Route::middleware(['auth', 'role:admin'])->group(function () {
|
||||||
Route::get('/admin/dashboard', [AdminController::class, 'index'])->name('admin.dashboard');
|
Route::get('/admin/dashboard', [AdminController::class, 'index'])->name('admin.dashboard')->middleware('verify-ms-session');
|
||||||
Route::post('/admin/apps', [AdminController::class, 'storeApp'])->name('admin.apps.store');
|
Route::post('/admin/apps', [AdminController::class, 'storeApp'])->name('admin.apps.store');
|
||||||
Route::delete('/admin/apps/{id}', [AdminController::class, 'destroyApp'])->name('admin.apps.destroy');
|
Route::delete('/admin/apps/{id}', [AdminController::class, 'destroyApp'])->name('admin.apps.destroy');
|
||||||
Route::post('/admin/users/{id}/toggle-block', [AdminController::class, 'toggleBlockUser'])->name('admin.users.toggle-block');
|
Route::post('/admin/users/{id}/toggle-block', [AdminController::class, 'toggleBlockUser'])->name('admin.users.toggle-block');
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user