From 31467dc24a1c9085117d321c1bfb193f0a432e20 Mon Sep 17 00:00:00 2001 From: Ryan Rader Date: Sat, 23 May 2026 09:39:46 -0400 Subject: [PATCH] feat(portal): redirect to return_to on expired_jwt_token MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the customer portal JWT expires, the Apollo error handler now reads the ?return_to query param off the current URL and redirects there with ?refresh=1 appended. This lets a host app self-heal stale portal tabs by generating a fresh portal URL on the refresh=1 visit. If return_to isn't in the URL (token was minted without it), the current behavior is unchanged — sections render the generic "Something went wrong" state. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/core/apolloClient/init.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/core/apolloClient/init.ts b/src/core/apolloClient/init.ts index e372f85bf8..cbc0b4ed74 100644 --- a/src/core/apolloClient/init.ts +++ b/src/core/apolloClient/init.ts @@ -35,6 +35,10 @@ const AUTH_ERRORS = [ LagoApiError.Unauthorized, ] +// One-shot guard so concurrent expired-JWT errors (the portal fires 5+ +// queries on load, all 401 at once) only trigger a single redirect. +let portalExpiryRedirected = false + const TIMEOUT = 300000 // 5 minutes timeout const { apiUrl, appVersion } = envGlobalVar() @@ -162,6 +166,26 @@ export const initializeApolloClient = async () => { window.location.pathname, ) + // When a customer-portal JWT expires, bounce the user back to the + // host app via the ?return_to= query param. The host app's + // /billing page reads ?refresh=1 and replaces the tab with a + // fresh portal URL, so the stale tab self-heals without the + // user seeing a broken portal. portalExpiryRedirected guards + // against firing for every in-flight query (5+ on portal load). + if ( + isCustomerPortal && + extensions?.code === LagoApiError.ExpiredJwtToken && + !portalExpiryRedirected + ) { + const returnTo = new URLSearchParams(window.location.search).get('return_to') + if (returnTo) { + portalExpiryRedirected = true + const sep = returnTo.includes('?') ? '&' : '?' + window.location.href = `${returnTo}${sep}refresh=1` + return + } + } + if (!isCustomerPortal && onAuthError) { onAuthError() }