From 573916c2423f94e0a0fdd5c24948c3e79cec8ce6 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Wed, 11 Nov 2026 20:42:01 +0100 Subject: [PATCH 1/2] refactor: forward set-cookie from the signOut response logout copied Set-Cookie values from a Headers object onto the Hono context through a hand-rolled helper. Hono's c.header replaces rather than appends, which is how two of the three clearing cookies were dropped before #103. Ask for the signOut response itself with asResponse: true and append its set-cookie values to the redirect Response. No code in this repo then decides how cookies are forwarded, so a future handler cannot lose headers the same way. forwardCookies and its module are gone. --- src/app/handlers/logout.js | 14 +++++++------- src/app/utils/cookies.js | 7 ------- 2 files changed, 7 insertions(+), 14 deletions(-) delete mode 100644 src/app/utils/cookies.js diff --git a/src/app/handlers/logout.js b/src/app/handlers/logout.js index 13279d4..99b632b 100644 --- a/src/app/handlers/logout.js +++ b/src/app/handlers/logout.js @@ -1,5 +1,4 @@ import { validateRedirectUrl } from "../utils/redirect.js"; -import { forwardCookies } from "../utils/cookies.js"; import { getAuthFromContext } from "../utils/auth.js"; /** @@ -13,14 +12,15 @@ export const logout = async (c) => { c.req.query("redirect_url"), "/?message=Bye!", ); - const status = await auth.api.signOut({ + const signOut = await auth.api.signOut({ headers: c.req.raw.headers, - returnHeaders: true, + asResponse: true, }); - // forward headers (cookies) - if (status.headers) { - forwardCookies(status.headers, c); + + const response = c.redirect(callbackURL); + for (const cookie of signOut.headers.getSetCookie()) { + response.headers.append("Set-Cookie", cookie); } - return c.redirect(callbackURL); + return response; }; diff --git a/src/app/utils/cookies.js b/src/app/utils/cookies.js deleted file mode 100644 index 2f19b1c..0000000 --- a/src/app/utils/cookies.js +++ /dev/null @@ -1,7 +0,0 @@ -// Helper function to forward Set-Cookie headers from better-auth response -export const forwardCookies = (headers, c) => { - const setCookieHeaders = headers.getSetCookie?.() || []; - setCookieHeaders.forEach((cookie) => { - c.header("Set-Cookie", cookie, { append: true }); - }); -}; From 57419bc2000ffeb004448cf8469d98432d146d33 Mon Sep 17 00:00:00 2001 From: Morgan Roderick Date: Wed, 11 Nov 2026 20:48:26 +0100 Subject: [PATCH 2/2] fix: surface a failed signOut on logout asResponse makes better-auth return APIErrors as error responses instead of throwing them (dispatch.mjs:193,249,258). logout never read the status, so a failed signOut redirected to /?message=Bye! while the session still stood. Return the signOut response and log the status when it is not ok. --- src/app/handlers/logout.js | 5 +++++ test/integration/cookie-attributes.test.js | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/app/handlers/logout.js b/src/app/handlers/logout.js index 99b632b..043629d 100644 --- a/src/app/handlers/logout.js +++ b/src/app/handlers/logout.js @@ -16,6 +16,11 @@ export const logout = async (c) => { headers: c.req.raw.headers, asResponse: true, }); + // asResponse makes better-auth return APIErrors instead of throwing them. + if (!signOut.ok) { + console.error(`Logout failed with status ${signOut.status}`); + return signOut; + } const response = c.redirect(callbackURL); for (const cookie of signOut.headers.getSetCookie()) { diff --git a/test/integration/cookie-attributes.test.js b/test/integration/cookie-attributes.test.js index b22d664..514a64e 100644 --- a/test/integration/cookie-attributes.test.js +++ b/test/integration/cookie-attributes.test.js @@ -156,3 +156,21 @@ test("logout clearing cookies keep their attributes", async (t) => { ); } }); + +test("logout surfaces a failed signOut instead of redirecting", async (t) => { + const auth = { + api: { + getSession: async () => null, + signOut: async () => + new Response(JSON.stringify({ message: "signOut failed" }), { + status: 500, + }), + }, + }; + const app = createApp(auth, null); + + const res = await app.request("/logout"); + + t.equal(res.status, 500, "propagates the signOut status"); + t.equal(res.headers.get("location"), null, "does not redirect"); +});