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 e2e/cloud/auth-session.test.ts (1 of 7 files). Show all

Unified · Split

e2e/cloud/auth-session.test.ts+21−3
@@ -132,7 +132,7 @@ scenario( · 131 unmodified lines
132);132);
133 133
134scenario(134scenario(
135 "Auth · logout sends the user home and tells the browser to drop the session",135 "Auth · logout ends the AuthKit session upstream and tells the browser to drop the cookie",
136 {},136 {},
137 Effect.gen(function* () {137 Effect.gen(function* () {
138 // Gate: the REST API plane is mounted on this target.138 // Gate: the REST API plane is mounted on this target.
@@ -148,10 +148,28 @@ scenario( · 9 unmodified lines
148 headers: identity.headers ?? {},148 headers: identity.headers ?? {},
149 }),149 }),
150 );150 );
151 expect(response.status, "logout redirects back to the landing page").toBe(302);151 expect(response.status, "logout hands the browser to WorkOS").toBe(302);
152 expect(response.headers.get("location"), "the destination is home").toBe("/");
153 const cleared = setCookieFor(response, "wos-session");152 const cleared = setCookieFor(response, "wos-session");
154 expect(cleared, "the session cookie is expired immediately").toContain("Max-Age=0");153 expect(cleared, "the session cookie is expired immediately").toContain("Max-Age=0");
155 expect(cleared, "the cookie value is wiped").toContain("wos-session=;");154 expect(cleared, "the cookie value is wiped").toContain("wos-session=;");
155
156 // The destination is WorkOS's session-end endpoint carrying this
157 // session's id — without this hop the hosted AuthKit session survives
158 // and the next sign-in silently re-authenticates.
159 const logoutUrl = new URL(response.headers.get("location") ?? "");
160 expect(logoutUrl.pathname, "the destination ends the WorkOS session").toBe(
161 "/user_management/sessions/logout",
162 );
163 expect(
164 logoutUrl.searchParams.get("session_id"),
165 "the WorkOS session being ended is this identity's",
166 ).toMatch(/^session_/);
167
168 const upstream = yield* Effect.promise(() => fetch(logoutUrl, { redirect: "manual" }));
169 expect(upstream.status, "WorkOS ends the session and sends the user onward").toBe(302);
170 expect(
171 upstream.headers.get("location"),
172 "the sign-out return URL lands back on this deployment",
173 ).toBe(new URL("/", target.baseUrl).toString());
156 }),174 }),
157);175);