fix(auth): stop stale session cookies signing users out on refresh

@supabase/ssr 0.1.0 never removed the old unchunked session cookie once a
refreshed session grew past one cookie, and always read that stale copy
first. Every load then retried an already-used refresh token, which the
hosted auth server rejects, signing the user out.

Upgrade @supabase/ssr to 0.12 (and supabase-js to match) and move to the
getAll/setAll cookie API, which clears stale chunks when writing.
This commit is contained in:
Josh Creek
2026-09-14 16:19:20 +01:00
parent f382176804
commit b7d2db4959
5 changed files with 97 additions and 129 deletions
+67 -91
View File
@@ -8,8 +8,8 @@
"name": "livingdextracker", "name": "livingdextracker",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"@supabase/ssr": "^0.1.0", "@supabase/ssr": "^0.12.7",
"@supabase/supabase-js": "^2.42.0", "@supabase/supabase-js": "^2.116.0",
"nanoid": "^5.0.4", "nanoid": "^5.0.4",
"sharp": "^0.33.4" "sharp": "^0.33.4"
}, },
@@ -3728,103 +3728,120 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@supabase/auth-js": { "node_modules/@supabase/auth-js": {
"version": "2.89.0", "version": "2.116.0",
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.89.0.tgz", "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.116.0.tgz",
"integrity": "sha512-wiWZdz8WMad8LQdJMWYDZ2SJtZP5MwMqzQq3ehtW2ngiI3UTgbKiFrvMUUS3KADiVlk4LiGfODB2mrYx7w2f8w==", "integrity": "sha512-Cmosty12gyKGK9N3bQb+lMmuAFev5nmUzaR1AsmZHqKOAGzqX1VQzmp49CNPwOx/pw0H9Qqk4rs9yhwTlKpfDg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"tslib": "2.8.1" "tslib": "2.8.1"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=22.0.0"
} }
}, },
"node_modules/@supabase/functions-js": { "node_modules/@supabase/functions-js": {
"version": "2.89.0", "version": "2.116.0",
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.89.0.tgz", "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.116.0.tgz",
"integrity": "sha512-XEueaC5gMe5NufNYfBh9kPwJlP5M2f+Ogr8rvhmRDAZNHgY6mI35RCkYDijd92pMcNM7g8pUUJov93UGUnqfyw==", "integrity": "sha512-E+VOc2QDcni/fySqkBFiZhnoB3SGydEdZgFI6/dEAGAHx6yEhB46TN9qb2wXs+E+RSzOBV0R6dasiSlw4xlZAA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"tslib": "2.8.1" "tslib": "2.8.1"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=22.0.0"
} }
}, },
"node_modules/@supabase/phoenix": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.5.tgz",
"integrity": "sha512-aAn9H9ovVyeApKy11OWOrrOGq8DV68yWeH4ud2lN9fzn4aO8Zb5GLL9m1pUg9nLqIcT+ZDfAcsZe0E/nqdv2lw==",
"license": "MIT"
},
"node_modules/@supabase/postgrest-js": { "node_modules/@supabase/postgrest-js": {
"version": "2.89.0", "version": "2.116.0",
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.89.0.tgz", "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.116.0.tgz",
"integrity": "sha512-/b0fKrxV9i7RNOEXMno/I1862RsYhuUo+Q6m6z3ar1f4ulTMXnDfv0y4YYxK2POcgrOXQOgKYQx1eArybyNvtg==", "integrity": "sha512-kGpVZTDHxFTJS3tu+rU0iTAZ+4U0bcLVjxwCk8f3gRhjw3qdCZjTBlgYvc4kGH2XccmAzbkKwXL/mrNHMGSc+A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"tslib": "2.8.1" "tslib": "2.8.1"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=22.0.0"
} }
}, },
"node_modules/@supabase/realtime-js": { "node_modules/@supabase/realtime-js": {
"version": "2.89.0", "version": "2.116.0",
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.89.0.tgz", "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.116.0.tgz",
"integrity": "sha512-aMOvfDb2a52u6PX6jrrjvACHXGV3zsOlWRzZsTIOAJa0hOVvRp01AwC1+nLTGUzxzezejrYeCX+KnnM1xHdl+w==", "integrity": "sha512-MHAnlXxi2s6yiJsZsQMfs2B3RFxeVfQWxerqYhIMqcCQV/FuY3LIeouPEkXw/ah7wUWMLYwempF9MOCUScyddg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/phoenix": "^1.6.6", "@supabase/phoenix": "0.4.5",
"@types/ws": "^8.18.1", "tslib": "2.8.1"
"tslib": "2.8.1",
"ws": "^8.18.2"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=22.0.0"
} }
}, },
"node_modules/@supabase/ssr": { "node_modules/@supabase/ssr": {
"version": "0.1.0", "version": "0.12.7",
"resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.1.0.tgz", "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.12.7.tgz",
"integrity": "sha512-bIVrkqjAK5G3KjkIMKYKtAOlCgRRplEWjrlyRyXSOYtgDieiOhk2ZyNAPsEOa1By9OZVxuX5eAW1fitdnuxayw==", "integrity": "sha512-wiBtEie1KkRJi9RrZWY3R2imRhX1JY7qMyUCH2z9AUk15gQebNEplM+urbCKamdxaTJLXUU6LlpkJsaxhojCEg==",
"license": "MIT",
"dependencies": { "dependencies": {
"cookie": "^0.5.0", "cookie": "^1.0.2"
"ramda": "^0.29.0"
}, },
"peerDependencies": { "peerDependencies": {
"@supabase/supabase-js": "^2.33.1" "@supabase/supabase-js": "^2.114.0"
} }
}, },
"node_modules/@supabase/ssr/node_modules/cookie": { "node_modules/@supabase/ssr/node_modules/cookie": {
"version": "0.5.0", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 0.6" "node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
} }
}, },
"node_modules/@supabase/storage-js": { "node_modules/@supabase/storage-js": {
"version": "2.89.0", "version": "2.116.0",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.89.0.tgz", "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.116.0.tgz",
"integrity": "sha512-6zKcXofk/M/4Eato7iqpRh+B+vnxeiTumCIP+Tz26xEqIiywzD9JxHq+udRrDuv6hXE+pmetvJd8n5wcf4MFRQ==", "integrity": "sha512-6/3hR6vccBP6oGM5B6RfbwZcTCKmQOodd/ZWQdsw8yJsU5zO/a//oBL6yLnmgxcjnHSrelW8rsO7hL5DPybyUQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"iceberg-js": "^0.8.1", "iceberg-js": "^0.8.1",
"tslib": "2.8.1" "tslib": "2.8.1"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=22.0.0"
} }
}, },
"node_modules/@supabase/supabase-js": { "node_modules/@supabase/supabase-js": {
"version": "2.89.0", "version": "2.116.0",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.89.0.tgz", "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.116.0.tgz",
"integrity": "sha512-KlaRwSfFA0fD73PYVMHj5/iXFtQGCcX7PSx0FdQwYEEw9b2wqM7GxadY+5YwcmuEhalmjFB/YvqaoNVF+sWUlg==", "integrity": "sha512-YyWmKXt2NspV9iO8FPnlswUFJIRnrLd3oTCb+3ZyYRuKZtBH0xCUDgnUqoyA0fGUxpM/UhfwDjYf/dht/9bp7g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@supabase/auth-js": "2.89.0", "@supabase/auth-js": "2.116.0",
"@supabase/functions-js": "2.89.0", "@supabase/functions-js": "2.116.0",
"@supabase/postgrest-js": "2.89.0", "@supabase/postgrest-js": "2.116.0",
"@supabase/realtime-js": "2.89.0", "@supabase/realtime-js": "2.116.0",
"@supabase/storage-js": "2.89.0" "@supabase/storage-js": "2.116.0"
}, },
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=22.0.0"
},
"peerDependencies": {
"@opentelemetry/api": ">=1.0.0"
},
"peerDependenciesMeta": {
"@opentelemetry/api": {
"optional": true
}
} }
}, },
"node_modules/@surma/rollup-plugin-off-main-thread": { "node_modules/@surma/rollup-plugin-off-main-thread": {
@@ -4016,16 +4033,12 @@
"version": "20.12.5", "version": "20.12.5",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.5.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.5.tgz",
"integrity": "sha512-BD+BjQ9LS/D8ST9p5uqBxghlN+S42iuNxjsUGjeZobe/ciXzk2qb1B6IXc6AnRLS+yFJRpN2IPEHMzwspfDJNw==", "integrity": "sha512-BD+BjQ9LS/D8ST9p5uqBxghlN+S42iuNxjsUGjeZobe/ciXzk2qb1B6IXc6AnRLS+yFJRpN2IPEHMzwspfDJNw==",
"dev": true,
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~5.26.4" "undici-types": "~5.26.4"
} }
}, },
"node_modules/@types/phoenix": {
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz",
"integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==",
"license": "MIT"
},
"node_modules/@types/pug": { "node_modules/@types/pug": {
"version": "2.0.10", "version": "2.0.10",
"resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz", "resolved": "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz",
@@ -4062,15 +4075,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.1.1.tgz",
@@ -9020,15 +9024,6 @@
} }
] ]
}, },
"node_modules/ramda": {
"version": "0.29.1",
"resolved": "https://registry.npmjs.org/ramda/-/ramda-0.29.1.tgz",
"integrity": "sha512-OfxIeWzd4xdUNxlWhgFazxsA/nl3mS4/jGZI5n00uWOoSSFRhC1b6gl6xvmzUamgmqELraWp0J/qqVlXYPDPyA==",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ramda"
}
},
"node_modules/randombytes": { "node_modules/randombytes": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@@ -10908,7 +10903,9 @@
"node_modules/undici-types": { "node_modules/undici-types": {
"version": "5.26.5", "version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true,
"peer": true
}, },
"node_modules/unicode-canonical-property-names-ecmascript": { "node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.0", "version": "2.0.0",
@@ -12268,27 +12265,6 @@
"node": "^20.17.0 || >=22.9.0" "node": "^20.17.0 || >=22.9.0"
} }
}, },
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xmlbuilder": { "node_modules/xmlbuilder": {
"version": "15.1.1", "version": "15.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
+2 -2
View File
@@ -78,8 +78,8 @@
}, },
"type": "module", "type": "module",
"dependencies": { "dependencies": {
"@supabase/ssr": "^0.1.0", "@supabase/ssr": "^0.12.7",
"@supabase/supabase-js": "^2.42.0", "@supabase/supabase-js": "^2.116.0",
"nanoid": "^5.0.4", "nanoid": "^5.0.4",
"sharp": "^0.33.4" "sharp": "^0.33.4"
}, },
+8 -11
View File
@@ -1,23 +1,20 @@
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public'; import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
import { createServerClient } from '@supabase/ssr'; import { createServerClient } from '@supabase/ssr';
import type { Handle } from '@sveltejs/kit'; import type { Handle } from '@sveltejs/kit';
import type { CookieSerializeOptions } from 'cookie';
export const handle: Handle = async ({ event, resolve }) => { export const handle: Handle = async ({ event, resolve }) => {
event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
cookies: { cookies: {
get: (key: string) => event.cookies.get(key), getAll: () => event.cookies.getAll(),
/** /**
* Note: You have to add the `path` variable to the * Note: You have to add the `path` variable to the set method due to sveltekit's cookie
* set and remove method due to sveltekit's cookie API * API requiring this to be set, setting the path to '/' will replicate previous/standard
* requiring this to be set, setting the path to an empty string * behaviour (https://kit.svelte.dev/docs/types#public-types-cookies)
* will replicate previous/standard behaviour (https://kit.svelte.dev/docs/types#public-types-cookies)
*/ */
set: (key: string, value: string, options: CookieSerializeOptions) => { setAll: (cookiesToSet) => {
event.cookies.set(key, value, { ...options, path: '/' }); cookiesToSet.forEach(({ name, value, options }) => {
}, event.cookies.set(name, value, { ...options, path: '/' });
remove: (key: string, options: CookieSerializeOptions) => { });
event.cookies.delete(key, { ...options, path: '/' });
} }
} }
}); });
+4 -2
View File
@@ -1,10 +1,12 @@
import type { LayoutServerLoad } from './$types'; import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals: { safeGetSession } }) => { export const load: LayoutServerLoad = async ({ locals: { safeGetSession }, cookies }) => {
const { session, user } = await safeGetSession(); const { session, user } = await safeGetSession();
return { return {
session, session,
user user,
// The universal layout load rebuilds a server-side client from these during SSR.
cookies: cookies.getAll()
}; };
}; };
+16 -23
View File
@@ -1,7 +1,6 @@
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public'; import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public';
import type { LayoutLoad } from './$types'; import type { LayoutLoad } from './$types';
import { createBrowserClient, isBrowser, parse, serialize } from '@supabase/ssr'; import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr';
import type { CookieSerializeOptions } from 'cookie';
export const load: LayoutLoad = async ({ fetch, data, depends }) => { export const load: LayoutLoad = async ({ fetch, data, depends }) => {
depends('supabase:auth'); depends('supabase:auth');
@@ -31,29 +30,23 @@ export const load: LayoutLoad = async ({ fetch, data, depends }) => {
return response; return response;
}; };
const supabase = createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { // The browser client manages document.cookie itself, including removing stale session chunks.
global: { const supabase = isBrowser()
fetch: authFetch ? createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
}, global: {
cookies: { fetch: authFetch
get(key: string) {
if (!isBrowser()) {
return JSON.stringify(data.session);
} }
})
const cookie = parse(document.cookie); : createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
return cookie[key]; global: {
}, fetch
set(key: string, value: string, options: CookieSerializeOptions) { },
if (isBrowser()) document.cookie = serialize(key, value, { ...options, path: '/' }); cookies: {
}, getAll() {
remove(key: string, options: CookieSerializeOptions) { return data.cookies;
if (isBrowser()) { }
document.cookie = serialize(key, '', { ...options, path: '/', maxAge: 0 });
} }
} });
}
});
/** /**
* It's fine to use `getSession` here, because on the client, `getSession` is * It's fine to use `getSession` here, because on the client, `getSession` is