Assistance with SolidStart and Supabase Auth in SSR

I am currently trying to piece together exactly how to setup SolidStart with the @supabase/ssr package. The Supabase package expects:
import { createBrowserClient, createServerClient, type CookieOptions } from '@supabase/ssr'

export function createSupabaseFrontendClient() {
return createBrowserClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY,
)
}

export function createSupabaseBackendClient() {
return createServerClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_SERVICE_KEY,
{
cookies: {
get: (
key: string,
): Promise<string | null | undefined> | string | null | undefined => {
// get the cookie value from the request
},
set: (key: string, value: string, options: CookieOptions): Promise<void> | void => {
// set the cookie value in the response
},
remove: (key: string, options: CookieOptions): Promise<void> | void => {
// remove the cookie from the session
},
},
},
)
}
import { createBrowserClient, createServerClient, type CookieOptions } from '@supabase/ssr'

export function createSupabaseFrontendClient() {
return createBrowserClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY,
)
}

export function createSupabaseBackendClient() {
return createServerClient(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_SERVICE_KEY,
{
cookies: {
get: (
key: string,
): Promise<string | null | undefined> | string | null | undefined => {
// get the cookie value from the request
},
set: (key: string, value: string, options: CookieOptions): Promise<void> | void => {
// set the cookie value in the response
},
remove: (key: string, options: CookieOptions): Promise<void> | void => {
// remove the cookie from the session
},
},
},
)
}
However, I am a little lost on how to setup this up with vinxi/http.
8 Replies
peerreynders
peerreynders•2mo ago
Disclosure: I know nothing of Supabase. However the cookie API is defined by h3's cookies-es. The first challenge that I'm seeing is that it expects an interface to modify cookies without the explicit context of the associated request (I'm assuming createServerClient is only created once during the server's lifetime-if however a new one is created for each request then matters could be considerably simpler). The requestEvent can sometimes be accessed via getRequestEvent() but FetchEvent available in middleware is a more reliable place to deal with cookies especially if cookies are going to be updated. Start has a habit of trying to stream a response ASAP (once out of the middleware; which can be delayed with deferStream: true on createAsync) so it's a good idea to be finished with any cookie updates before the request even leaves the middleware.
SolidStart Release Candidate Documentation
SolidStart Release Candidate Documentation
Early release documentation and resources for SolidStart Release Candidate
peerreynders
peerreynders•2mo ago
If I'm understanding this code correctly vinxi allows you to use h3 functions without the H3 Event (at the arguments[0] position) and will supply it from the store provided to AsyncLocalStorage. So it should be possible to run createSupabaseBackendClient in entry-server.tsx and assign the client to a module global.
GitHub
solid-start/examples/with-auth/src/entry-server.tsx at 486edc6d385b...
SolidStart, the Solid app framework. Contribute to solidjs/solid-start development by creating an account on GitHub.
GitHub
vinxi/packages/vinxi/runtime/http.js at 6f3d81beabc8cf003c0095eb9b4...
The Full Stack JavaScript SDK. Contribute to nksaraf/vinxi development by creating an account on GitHub.
peerreynders
peerreynders•2mo ago
Once the client exists you should be able to use it in middleware to do it's thing (most importantly get the set/remove out of the way).
DaOfficialWizard🧙
Thanks for your input @peerreynders - this is the conclusion that i've come to as well. I am just struggling with the implementation - being new to SolidStart it is not entirely clear to me what the flow is for middleware.
if however a new one is created for each request then matters could be considerably simpler)
Since the supabase/ssr package exposes the client as a singleton i think this can be achieved. this sounds interesting, could you elaborate a bit? I am not seeing where i would assign the client to the module. The trouble i am running into - how do i setup the createServerClient override methods using the middleware strategy? I can grab the nativeEvent and pass that to the vinxi helpers - but that doesn't really help me setup the client? Should i construct the client inside the onRequest callback and then assign it to the global context? Is that possible? That seems silly ...
export function createSupabaseBackendClient() {
return createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!, {
cookies: {
get: (key: string) => {
// get the cookie value from the request
return getCookie(key)
},
set: (key: string, value: string, options: CookieOptions) =>{
// set the cookie value in the response
setCookie(key, value, options)
},
remove: (key: string, options: CookieOptions) =>{
// remove the cookie from the session
deleteCookie(key, options)
},
},
})
}
export function createSupabaseBackendClient() {
return createServerClient(process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_KEY!, {
cookies: {
get: (key: string) => {
// get the cookie value from the request
return getCookie(key)
},
set: (key: string, value: string, options: CookieOptions) =>{
// set the cookie value in the response
setCookie(key, value, options)
},
remove: (key: string, options: CookieOptions) =>{
// remove the cookie from the session
deleteCookie(key, options)
},
},
})
}
import { createHandler, StartServer } from '@solidjs/start/server'
import { createSupabaseBackendClient } from '@src/server/db'

export default createHandler(() => {
if (!global.supabaseClient) {
global.supabaseClient = createSupabaseBackendClient()
}
return (
<StartServer
document={({ assets, children, scripts }) => (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
{assets}
</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
)
})
import { createHandler, StartServer } from '@solidjs/start/server'
import { createSupabaseBackendClient } from '@src/server/db'

export default createHandler(() => {
if (!global.supabaseClient) {
global.supabaseClient = createSupabaseBackendClient()
}
return (
<StartServer
document={({ assets, children, scripts }) => (
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
{assets}
</head>
<body>
<div id="app">{children}</div>
{scripts}
</body>
</html>
)}
/>
)
})
This is what i have 🙂 Now ... does this look right xD
server: {
//preset: 'vercel',
experimental: { asyncContext: true, openAPI: true },
},
server: {
//preset: 'vercel',
experimental: { asyncContext: true, openAPI: true },
},
I have set this as my server in the app.config.ts No, that must not be it ... i am getting a Error: Module "node:async_hooks" has been externalized for browser compatibility. Cannot access "node:async_hooks.AsyncLocalStorage" in client code. So, i must have to do something else.
peerreynders
peerreynders•2mo ago
I'm confused by the "use server" here. This is purely server side code so don't include it in a module that may contain code that is used in the browser;
DaOfficialWizard🧙
you're 100% correct - that was a lingering bit from old code 🙂 I solved this particular error - i had to create the frontend client in a different file entirely.
peerreynders
peerreynders•2mo ago
If you are using the with-auth example as a starting point you would be using auth.signUp or auth.signInWithPassword in the loginOrRegister action.
GitHub
solid-start/examples/with-auth/src/lib/index.ts at 486edc6d385bb0ce...
SolidStart, the Solid app framework. Contribute to solidjs/solid-start development by creating an account on GitHub.
peerreynders
peerreynders•2mo ago
By using actions you can update cookies safely because the response won't start streaming until you redirect. That way middleware doesn't have to get involved until you have protected routes (the middleware can return a redirect when the request is not authorized).