History

cloud: end the WorkOS session on sign out

Logout only cleared the local sealed-session cookie; the hosted AuthKit session survived, so the next Sign in silently re-authenticated without credentials (#1445). Follow WorkOS's documented sign-out: extract the session id from the sealed cookie locally (plain JWT decode, no verification, so an expired token still signs out), 302 through the WorkOS logout endpoint to end the session upstream, and land back on the public homepage via return_to. If the cookie will not unseal, fall back to the old redirect-to-/ so local sign-out always completes. The shell submits sign-out as a top-level form POST instead of fetch so the browser navigation carries WorkOS's cookies through the hop. Needs @executor-js/emulate 0.13.6 (the emulator's session-end route).

Rhys Sullivan committed 3 months ago

Commit 00a15021 parent 7d6fcea7 files · +119 −18Browse files

Showing the changes to apps/cloud/src/web (1 of 7 files). Show all

Unified · Split

apps/cloud/src/web/shell.tsx+11−4
@@ -9,7 +9,7 @@ import { SupportSlot } from "./components/support-slot"; · 8 unmodified lines
99 // ---------------------------------------------------------------------------
1010 // Cloud shell — the SHARED multiplayer shell, identical to self-host, with
1111 // cloud-only bits injected through its slots:
12−// - sign-out POST cloud's WorkOS logout, then redirect home
12+// - sign-out navigate through cloud's WorkOS logout, land home
1313 // - nav items defaults + Organization + Billing (cloud-only sections)
1414 // - org menu slot multi-org switcher + create-org dialog (cloud-only)
1515 // - support slot the "Get support" dialog button (cloud-only)
@@ -25,10 +25,17 @@ const navItems = [ · 9 unmodified lines
2525 { to: "/billing", label: "Billing" },
2626 ];
2727
28−const signOut = async () => {
29− await fetch(AUTH_PATHS.logout, { method: "POST" });
28+// A top-level form POST, not fetch: the logout response 302s through the
29+// WorkOS logout endpoint (ending the hosted AuthKit session) before landing
30+// back home. A fetch would follow that chain invisibly inside the XHR and
31+// leave the page where it was; the browser navigation is the logout.
32+const signOut = () => {
3033 trackEvent("signed_out");
31− window.location.href = "/";
34+ const form = document.createElement("form");
35+ form.method = "POST";
36+ form.action = AUTH_PATHS.logout;
37+ document.body.appendChild(form);
38+ form.submit();
3239 };
3340
3441 export function Shell(props: { readonly content?: React.ReactNode }) {