diff --git a/app/Http/Controllers/Auth/SsoLoginController.php b/app/Http/Controllers/Auth/SsoLoginController.php
index fb5fc1e..0da2a5a 100644
--- a/app/Http/Controllers/Auth/SsoLoginController.php
+++ b/app/Http/Controllers/Auth/SsoLoginController.php
@@ -138,6 +138,18 @@ class SsoLoginController extends Controller
return redirect()->away($this->defaultSignedOutUrl());
}
+ /** Platform session ended — clear this app and offer silent sign-in again. */
+ public function platformSignedOut(Request $request): RedirectResponse
+ {
+ Auth::logout();
+ $request->session()->invalidate();
+ $request->session()->regenerateToken();
+
+ return redirect()->route('sso.connect', [
+ 'redirect' => (string) $request->query('redirect', ''),
+ ]);
+ }
+
public function logoutBridge(Request $request): View
{
$return = $this->safeReturnUrl((string) $request->query('return', ''));
diff --git a/app/Http/Middleware/EnsurePlatformSession.php b/app/Http/Middleware/EnsurePlatformSession.php
new file mode 100644
index 0000000..db642f5
--- /dev/null
+++ b/app/Http/Middleware/EnsurePlatformSession.php
@@ -0,0 +1,58 @@
+headers->get('Cookie', '');
+ if ($cookieHeader === '') {
+ return $this->clearAppSession($request);
+ }
+
+ try {
+ $response = Http::withHeaders(['Cookie' => $cookieHeader])
+ ->timeout(3)
+ ->get('https://'.$authDomain.'/sso/ping');
+ } catch (\Throwable) {
+ return $next($request);
+ }
+
+ if ($response->status() === 401) {
+ return $this->clearAppSession($request);
+ }
+
+ return $next($request);
+ }
+
+ private function clearAppSession(Request $request): Response
+ {
+ Auth::logout();
+ $request->session()->invalidate();
+ $request->session()->regenerateToken();
+
+ return redirect()->route('sso.connect', [
+ 'redirect' => $request->fullUrl(),
+ ]);
+ }
+}
diff --git a/bootstrap/app.php b/bootstrap/app.php
index 0dffcc8..6504353 100644
--- a/bootstrap/app.php
+++ b/bootstrap/app.php
@@ -19,6 +19,9 @@ return Application::configure(basePath: dirname(__DIR__))
$middleware->web(append: [
\App\Http\Middleware\SetActingAccount::class,
]);
+ $middleware->alias([
+ 'platform.session' => \App\Http\Middleware\EnsurePlatformSession::class,
+ ]);
// External registrar webhook posts have no CSRF token.
$middleware->validateCsrfTokens(except: [
diff --git a/resources/views/partials/sso-keepalive.blade.php b/resources/views/partials/sso-keepalive.blade.php
index ecd5d4a..2d297c5 100644
--- a/resources/views/partials/sso-keepalive.blade.php
+++ b/resources/views/partials/sso-keepalive.blade.php
@@ -1,4 +1,21 @@
@if (auth()->check())
- {{-- Same-site iframe keeps the shared auth.ladill.com session warm while using this app. --}}
-
+ @php
+ $authPing = 'https://'.config('app.auth_domain').'/sso/ping';
+ $platformSignedOutUrl = route('sso.platform-signed-out', ['redirect' => url()->current()]);
+ @endphp
+
@endif
diff --git a/routes/web.php b/routes/web.php
index 570d2bf..2b05f30 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -28,9 +28,10 @@ Route::get('/sso/callback', [SsoLoginController::class, 'callback'])->name('sso.
Route::post('/logout', [SsoLoginController::class, 'logout'])->name('logout');
Route::get('/sso/logout-bridge', [SsoLoginController::class, 'logoutBridge'])->name('sso.logout-bridge');
Route::get('/sso/logout-frontchannel', [SsoLoginController::class, 'frontchannelLogout'])->name('sso.logout-frontchannel');
+Route::get('/sso/platform-signed-out', [SsoLoginController::class, 'platformSignedOut'])->name('sso.platform-signed-out');
Route::get('/signed-out', fn () => auth()->check() ? redirect()->route('hosting.dashboard') : view('hosting.signed-out'))->name('hosting.signed-out');
-Route::middleware(['auth'])->group(function () use ($serversApp) {
+Route::middleware(['auth', 'platform.session'])->group(function () use ($serversApp) {
Route::get('/notifications', [NotificationController::class, 'index'])->name('notifications.index');
Route::get('/notifications/unread', [NotificationController::class, 'unread'])->name('notifications.unread');
Route::post('/notifications/{id}/read', [NotificationController::class, 'markAsRead'])->name('notifications.mark-read');