diff --git a/e2e/src/web/specs/auth.e2e-spec.ts b/e2e/src/web/specs/auth.e2e-spec.ts
index e89f17a4e9..74bee64e0a 100644
--- a/e2e/src/web/specs/auth.e2e-spec.ts
+++ b/e2e/src/web/specs/auth.e2e-spec.ts
@@ -25,7 +25,7 @@ test.describe('Registration', () => {
 
     // login
     await expect(page).toHaveTitle(/Login/);
-    await page.goto('/auth/login');
+    await page.goto('/auth/login?autoLaunch=0');
     await page.getByLabel('Email').fill('admin@immich.app');
     await page.getByLabel('Password').fill('password');
     await page.getByRole('button', { name: 'Login' }).click();
@@ -59,7 +59,7 @@ test.describe('Registration', () => {
     await context.clearCookies();
 
     // login
-    await page.goto('/auth/login');
+    await page.goto('/auth/login?autoLaunch=0');
     await page.getByLabel('Email').fill('user@immich.cloud');
     await page.getByLabel('Password').fill('password');
     await page.getByRole('button', { name: 'Login' }).click();
@@ -72,7 +72,7 @@ test.describe('Registration', () => {
     await page.getByRole('button', { name: 'Change password' }).click();
 
     // login with new password
-    await expect(page).toHaveURL('/auth/login');
+    await expect(page).toHaveURL('/auth/login?autoLaunch=0');
     await page.getByLabel('Email').fill('user@immich.cloud');
     await page.getByLabel('Password').fill('new-password');
     await page.getByRole('button', { name: 'Login' }).click();
diff --git a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte
index f7f9b877f3..90f6b3c55b 100644
--- a/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte
+++ b/web/src/lib/components/shared-components/navigation-bar/navigation-bar.svelte
@@ -10,11 +10,13 @@
   import ImmichLogo from '$lib/components/shared-components/immich-logo.svelte';
   import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte';
   import { AppRoute } from '$lib/constants';
+  import { authManager } from '$lib/stores/auth-manager.svelte';
+  import { mobileDevice } from '$lib/stores/mobile-device.svelte';
   import { featureFlags } from '$lib/stores/server-config.store';
+  import { sidebarStore } from '$lib/stores/sidebar.svelte';
   import { user } from '$lib/stores/user.store';
   import { userInteraction } from '$lib/stores/user.svelte';
-  import { handleLogout } from '$lib/utils/auth';
-  import { getAboutInfo, logout, type ServerAboutResponseDto } from '@immich/sdk';
+  import { getAboutInfo, type ServerAboutResponseDto } from '@immich/sdk';
   import { Button, IconButton } from '@immich/ui';
   import { mdiHelpCircleOutline, mdiMagnify, mdiMenu, mdiTrayArrowUp } from '@mdi/js';
   import { onMount } from 'svelte';
@@ -23,8 +25,6 @@
   import ThemeButton from '../theme-button.svelte';
   import UserAvatar from '../user-avatar.svelte';
   import AccountInfoPanel from './account-info-panel.svelte';
-  import { sidebarStore } from '$lib/stores/sidebar.svelte';
-  import { mobileDevice } from '$lib/stores/mobile-device.svelte';
 
   interface Props {
     showUploadButton?: boolean;
@@ -38,11 +38,6 @@
   let shouldShowHelpPanel = $state(false);
   let innerWidth: number = $state(0);
 
-  const onLogout = async () => {
-    const { redirectUri } = await logout();
-    await handleLogout(redirectUri);
-  };
-
   let info: ServerAboutResponseDto | undefined = $state();
 
   onMount(async () => {
@@ -183,7 +178,7 @@
           {/if}
 
           {#if shouldShowAccountInfoPanel}
-            <AccountInfoPanel {onLogout} />
+            <AccountInfoPanel onLogout={() => authManager.logout()} />
           {/if}
         </div>
       </section>
diff --git a/web/src/lib/stores/auth-manager.svelte.ts b/web/src/lib/stores/auth-manager.svelte.ts
new file mode 100644
index 0000000000..72c966df0b
--- /dev/null
+++ b/web/src/lib/stores/auth-manager.svelte.ts
@@ -0,0 +1,33 @@
+import { goto } from '$app/navigation';
+import { AppRoute } from '$lib/constants';
+import { eventManager } from '$lib/stores/event-manager.svelte';
+import { logout } from '@immich/sdk';
+
+class AuthManager {
+  async logout() {
+    let redirectUri;
+
+    try {
+      const response = await logout();
+      if (response.redirectUri) {
+        redirectUri = response.redirectUri;
+      }
+    } catch (error) {
+      console.log('Error logging out:', error);
+    }
+
+    redirectUri = redirectUri ?? AppRoute.AUTH_LOGIN;
+
+    try {
+      if (redirectUri.startsWith('/')) {
+        await goto(redirectUri);
+      } else {
+        globalThis.location.href = redirectUri;
+      }
+    } finally {
+      eventManager.emit('auth.logout');
+    }
+  }
+}
+
+export const authManager = new AuthManager();
diff --git a/web/src/lib/stores/event-manager.svelte.ts b/web/src/lib/stores/event-manager.svelte.ts
new file mode 100644
index 0000000000..09e9b45c3c
--- /dev/null
+++ b/web/src/lib/stores/event-manager.svelte.ts
@@ -0,0 +1,54 @@
+type Listener<EventMap extends Record<string, unknown[]>, K extends keyof EventMap> = (...params: EventMap[K]) => void;
+
+class EventManager<EventMap extends Record<string, unknown[]>> {
+  private listeners: {
+    [K in keyof EventMap]?: {
+      listener: Listener<EventMap, K>;
+      once?: boolean;
+    }[];
+  } = {};
+
+  on<T extends keyof EventMap>(key: T, listener: (...params: EventMap[T]) => void) {
+    return this.addListener(key, listener, false);
+  }
+
+  once<T extends keyof EventMap>(key: T, listener: (...params: EventMap[T]) => void) {
+    return this.addListener(key, listener, true);
+  }
+
+  off<K extends keyof EventMap>(key: K, listener: Listener<EventMap, K>) {
+    if (this.listeners[key]) {
+      this.listeners[key] = this.listeners[key].filter((item) => item.listener !== listener);
+    }
+
+    return this;
+  }
+
+  emit<T extends keyof EventMap>(key: T, ...params: EventMap[T]) {
+    if (!this.listeners[key]) {
+      return;
+    }
+
+    for (const { listener } of this.listeners[key]) {
+      listener(...params);
+    }
+
+    // remove one time listeners
+    this.listeners[key] = this.listeners[key].filter((item) => !item.once);
+  }
+
+  private addListener<T extends keyof EventMap>(key: T, listener: (...params: EventMap[T]) => void, once: boolean) {
+    if (!this.listeners[key]) {
+      this.listeners[key] = [];
+    }
+
+    this.listeners[key].push({ listener, once });
+
+    return this;
+  }
+}
+
+export const eventManager = new EventManager<{
+  'user.login': [];
+  'auth.logout': [];
+}>();
diff --git a/web/src/lib/stores/folders.svelte.ts b/web/src/lib/stores/folders.svelte.ts
index fb59687a38..c6fc7808b2 100644
--- a/web/src/lib/stores/folders.svelte.ts
+++ b/web/src/lib/stores/folders.svelte.ts
@@ -1,3 +1,4 @@
+import { eventManager } from '$lib/stores/event-manager.svelte';
 import {
   getAssetsByOriginalPath,
   getUniqueOriginalPaths,
@@ -16,6 +17,10 @@ class FoldersStore {
   uniquePaths = $state<string[]>([]);
   assets = $state<AssetCache>({});
 
+  constructor() {
+    eventManager.on('auth.logout', () => this.clearCache());
+  }
+
   async fetchUniquePaths() {
     if (this.initialized) {
       return;
diff --git a/web/src/lib/stores/memory.store.svelte.ts b/web/src/lib/stores/memory.store.svelte.ts
index 7173b43d06..ef3f87a3aa 100644
--- a/web/src/lib/stores/memory.store.svelte.ts
+++ b/web/src/lib/stores/memory.store.svelte.ts
@@ -1,3 +1,4 @@
+import { eventManager } from '$lib/stores/event-manager.svelte';
 import { asLocalTimeISO } from '$lib/utils/date-time';
 import {
   type AssetResponseDto,
@@ -24,6 +25,10 @@ export type MemoryAsset = MemoryIndex & {
 };
 
 class MemoryStoreSvelte {
+  constructor() {
+    eventManager.on('auth.logout', () => this.clearCache());
+  }
+
   memories = $state<MemoryResponseDto[]>([]);
   private initialized = false;
   private memoryAssets = $derived.by(() => {
diff --git a/web/src/lib/stores/search.svelte.ts b/web/src/lib/stores/search.svelte.ts
index 7d012922ca..f334f53460 100644
--- a/web/src/lib/stores/search.svelte.ts
+++ b/web/src/lib/stores/search.svelte.ts
@@ -1,7 +1,13 @@
+import { eventManager } from '$lib/stores/event-manager.svelte';
+
 class SearchStore {
   savedSearchTerms = $state<string[]>([]);
   isSearchEnabled = $state(false);
 
+  constructor() {
+    eventManager.on('auth.logout', () => this.clearCache());
+  }
+
   clearCache() {
     this.savedSearchTerms = [];
     this.isSearchEnabled = false;
diff --git a/web/src/lib/stores/user.store.ts b/web/src/lib/stores/user.store.ts
index 5bffc08b80..fe2288c252 100644
--- a/web/src/lib/stores/user.store.ts
+++ b/web/src/lib/stores/user.store.ts
@@ -1,3 +1,4 @@
+import { eventManager } from '$lib/stores/event-manager.svelte';
 import { purchaseStore } from '$lib/stores/purchase.store';
 import { type UserAdminResponseDto, type UserPreferencesResponseDto } from '@immich/sdk';
 import { writable } from 'svelte/store';
@@ -14,3 +15,5 @@ export const resetSavedUser = () => {
   preferences.set(undefined as unknown as UserPreferencesResponseDto);
   purchaseStore.setPurchaseStatus(false);
 };
+
+eventManager.on('auth.logout', () => resetSavedUser());
diff --git a/web/src/lib/stores/user.svelte.ts b/web/src/lib/stores/user.svelte.ts
index 71b2cdd847..093d90e4b5 100644
--- a/web/src/lib/stores/user.svelte.ts
+++ b/web/src/lib/stores/user.svelte.ts
@@ -1,3 +1,4 @@
+import { eventManager } from '$lib/stores/event-manager.svelte';
 import type {
   AlbumResponseDto,
   ServerAboutResponseDto,
@@ -19,8 +20,10 @@ const defaultUserInteraction: UserInteractions = {
   serverInfo: undefined,
 };
 
-export const resetUserInteraction = () => {
+export const userInteraction = $state<UserInteractions>(defaultUserInteraction);
+
+const reset = () => {
   Object.assign(userInteraction, defaultUserInteraction);
 };
 
-export const userInteraction = $state<UserInteractions>(defaultUserInteraction);
+eventManager.on('auth.logout', () => reset());
diff --git a/web/src/lib/stores/websocket.ts b/web/src/lib/stores/websocket.ts
index d398ca52a9..90228a5cbd 100644
--- a/web/src/lib/stores/websocket.ts
+++ b/web/src/lib/stores/websocket.ts
@@ -1,5 +1,4 @@
-import { AppRoute } from '$lib/constants';
-import { handleLogout } from '$lib/utils/auth';
+import { authManager } from '$lib/stores/auth-manager.svelte';
 import { createEventEmitter } from '$lib/utils/eventemitter';
 import type { AssetResponseDto, ServerVersionResponseDto } from '@immich/sdk';
 import { io, type Socket } from 'socket.io-client';
@@ -50,7 +49,7 @@ websocket
   .on('disconnect', () => websocketStore.connected.set(false))
   .on('on_server_version', (serverVersion) => websocketStore.serverVersion.set(serverVersion))
   .on('on_new_release', (releaseVersion) => websocketStore.release.set(releaseVersion))
-  .on('on_session_delete', () => handleLogout(AppRoute.AUTH_LOGIN))
+  .on('on_session_delete', () => authManager.logout())
   .on('connect_error', (e) => console.log('Websocket Connect Error', e));
 
 export const openWebsocketConnection = () => {
diff --git a/web/src/lib/utils/auth.ts b/web/src/lib/utils/auth.ts
index 22b92dd988..9b78c345e2 100644
--- a/web/src/lib/utils/auth.ts
+++ b/web/src/lib/utils/auth.ts
@@ -1,11 +1,7 @@
 import { browser } from '$app/environment';
-import { goto } from '$app/navigation';
-import { foldersStore } from '$lib/stores/folders.svelte';
-import { memoryStore } from '$lib/stores/memory.store.svelte';
 import { purchaseStore } from '$lib/stores/purchase.store';
-import { searchStore } from '$lib/stores/search.svelte';
-import { preferences as preferences$, resetSavedUser, user as user$ } from '$lib/stores/user.store';
-import { resetUserInteraction, userInteraction } from '$lib/stores/user.svelte';
+import { preferences as preferences$, user as user$ } from '$lib/stores/user.store';
+import { userInteraction } from '$lib/stores/user.svelte';
 import { getAboutInfo, getMyPreferences, getMyUser, getStorage } from '@immich/sdk';
 import { redirect } from '@sveltejs/kit';
 import { DateTime } from 'luxon';
@@ -91,19 +87,3 @@ export const getAccountAge = (): number => {
 
   return Number(accountAge);
 };
-
-export const handleLogout = async (redirectUri: string) => {
-  try {
-    if (redirectUri.startsWith('/')) {
-      await goto(redirectUri);
-    } else {
-      globalThis.location.href = redirectUri;
-    }
-  } finally {
-    resetSavedUser();
-    resetUserInteraction();
-    foldersStore.clearCache();
-    memoryStore.clearCache();
-    searchStore.clearCache();
-  }
-};
diff --git a/web/src/routes/auth/change-password/+page.svelte b/web/src/routes/auth/change-password/+page.svelte
index 33d354552e..16a6ffc677 100644
--- a/web/src/routes/auth/change-password/+page.svelte
+++ b/web/src/routes/auth/change-password/+page.svelte
@@ -1,9 +1,8 @@
 <script lang="ts">
-  import { goto } from '$app/navigation';
   import AuthPageLayout from '$lib/components/layouts/AuthPageLayout.svelte';
-  import { AppRoute } from '$lib/constants';
-  import { resetSavedUser, user } from '$lib/stores/user.store';
-  import { logout, updateMyUser } from '@immich/sdk';
+  import { authManager } from '$lib/stores/auth-manager.svelte';
+  import { user } from '$lib/stores/user.store';
+  import { updateMyUser } from '@immich/sdk';
   import { Alert, Button, Field, HelperText, PasswordInput, Stack, Text } from '@immich/ui';
   import { t } from 'svelte-i18n';
   import type { PageData } from './$types';
@@ -25,9 +24,7 @@
     }
 
     await updateMyUser({ userUpdateMeDto: { password } });
-    await goto(AppRoute.AUTH_LOGIN);
-    resetSavedUser();
-    await logout();
+    await authManager.logout();
   };
 </script>