diff --git a/src/lib/apis/index.ts b/src/lib/apis/index.ts index dbeef70a0..71af8f793 100644 --- a/src/lib/apis/index.ts +++ b/src/lib/apis/index.ts @@ -1406,6 +1406,32 @@ export const getBackendConfig = async () => { }); if (error) { + // When a forward-auth proxy (e.g. Authentik/Traefik) intercepts the + // request and redirects to an external login page, the browser blocks + // the cross-origin redirect for fetch() and throws a TypeError. + // Detect this by re-fetching with redirect:"manual" — if the server + // responded with a redirect, the probe returns an opaque redirect + // response instead of throwing, confirming the backend is alive but + // an auth proxy is intercepting. + if (error instanceof TypeError) { + try { + const probeRes = await fetch(`${WEBUI_BASE_URL}/api/config`, { + method: 'GET', + credentials: 'include', + redirect: 'manual', + headers: { 'Content-Type': 'application/json' } + }); + if ( + probeRes.type === 'opaqueredirect' || + (probeRes.status >= 300 && probeRes.status < 400) + ) { + throw { authRedirect: true }; + } + } catch (probeErr: any) { + if (probeErr?.authRedirect) throw probeErr; + // Probe also failed — genuine network/backend issue + } + } throw error; } diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 0c539db03..920e56f96 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -906,6 +906,12 @@ backendConfig = await getBackendConfig(); console.log('Backend config:', backendConfig); } catch (error) { + if (error?.authRedirect) { + // Forward-auth proxy is redirecting to an external login page. + // Full-page navigation lets the browser follow the redirect natively. + window.location.href = '/'; + return; + } console.error('Error loading backend config:', error); } // Initialize i18n even if we didn't get a backend config,