https://supabase.com/ logo
I'm having some trouble with OTP auth: ``` await ...
# help
a
I'm having some trouble with OTP auth:
Copy code
await supabase.auth.signIn({email})
This triggers an email, and when the verification link is clicked, it redirects to http://localhost:5173/#access_token=[...]&expires_in=3600&refresh_token=[...]&token_type=bearer&type=magiclink AFAIK that Supabase -> GoTrue -> redirect flow should set browser cookies, but that's not happening and I'm not sure why
n
Hello @Aris! This thread has been automatically created from your message in #843999948717555735 a few seconds ago. We have already mentioned the @User so that they can see your message and help you as soon as possible! Want to unsubscribe from this thread? Right-click the thread in Discord (or use the ``...`` menu) and select "Leave Thread" to unsubscribe from future updates. Want to change the title? Use the ``/title`` command! We have solved your problem? Click the button below to archive it.
s
Please provide more information about what your code looks like
a
Sure!
Copy code
<script lang="ts">
    import {provider} from "$lib/auth"
    async function submit() {
        const email = "temporarily-hardcoded@example.com"
        const res = await provider.signIn(
            {email}
            // temporarily removed
            // {
            //     shouldCreateUser: true,
            //     redirectTo: "/auth/otp-ok",
            // }
        )
        console.log("otp submit", res)
    }
</script>

<form on:submit|preventDefault={submit}>
    <div>
        <input
            type="email"
            id="email"
            name="email"
            autocomplete="email"
            placeholder="me@example.com"
            required
        />
    </div>
    <button type="submit">Continue with email</button>
</form>
Copy code
import {createClient} from "@supabase/supabase-js"

const supabase = createClient(
    "https://....supabase.co",
    "..."
)

export const provider = supabase.auth
s
Can you provide the code for the page it returns to when you click the email link?
a
It redirects to
/
because Supabase doesn't seem to support custom redirects for Magic links. So I'm only doing logging to debug this:
Copy code
// hooks.ts

export const handle: Handle = async ({event, resolve}) => {
    const {
        locals,
        params,
        request: {method, headers},
        url: {pathname},
    } = event

    const cookies = cookie.parse(headers.get("cookie") ?? "")

    console.log(`server: handle ${method} ${pathname}`, locals, params, cookies)
    console.log(headers)

    const res = await resolve(event)
    return res
s
Looks like you are using SvelteKit, you wouldn't be able to parse the cookie as its a domain specific cookie. This cookie is stored on the supabase domain
Have you had a look at the
@supabase/auth-helpers-sveltekit
which handles this for you and also creates a cookie on your own domain?
a
Yes, I have. I may have missed the section that sets a cookie for me. That's basically what I'm trying to do here.
s
I don't see any part of this code where you are using the auth-helpers
a
I've had a look at the library, I'm not using it
I believe handleSession() is the relevant function?
Copy code
const cookies = parseCookie(req.headers.get('cookie'));
This is what I'm trying to do as well.
I've also looked at the
/callback
endpoint, which is not applicable when using a Magic link?
s
In the case of the library its parsing a cookie that is set on its own domain, so the library saves that cookie itself
In your code you are checking for a cookie that doesn't exist because one wasn't saved by your code
Definitely applicable as this is where the cookie saving happens
Here is a old version of a project of mine that wasn't using auth-helpers, but also this is an old version of SvelteKit too, but this is how I was handling it in that project https://github.com/silentworks/waiting-list/blob/archive/0.0.2/src/lib/handleAuth.js
You need this bit of client side code to get the accessToken from the redirect and then pass this to your
handleAuth
hook https://github.com/silentworks/waiting-list/blob/archive/0.0.2/src/routes/__layout.svelte#L8-L21
a
With how quickly they update, all versions of SvelteKit are old versions. 😛
s
Yeah still pre-1.0 so breaking changes happen
a
Checking now...I see in the /callback endpoint they check
request.json()
, if Supabase is POSTing that on the redirect then I believe that's what I need to parse
s
No Supabase isn't POSTing anything to the server, everything happens on the client. The second link I posted in the message above highlights that
a
From your code:
Copy code
const expressStyleRequest = await toExpressRequest(request)
const { user } = await supabase.auth.api.getUserByCookie(expressStyleRequest)
Copy code
export async function toExpressRequest(req, body = {}) {
    return {
        body,
        headers: { host: req.headers.get('host') },
        cookies: cookie.parse(req.headers.get('cookie') || '')
    }
}
s
As stated before that is old SvelteKit, it won't work with current version at all
It's better to study the
@supabase/auth-helpers-sveltekit
and
@supabase/auth-helpers-svelte
libraries as they work with current SvelteKit.
a
Sure. I've reviewed those codebases in depth, so I think we're missing each other a bit. Here's what we've discussed: My code is trying to check a cookie that doesn't exist. On OTP signin, the flow is verify email -> Supabase -> (gotrue under the hood) -> redirect to http:/localhost:5173/#access_token=... with no cookie header That makes sense. What I don't understand is that all of the referenced code examples (auth-helpers, your project) seem to be checking that exact same cookie, and that works for them. If I used those libs myself, they would be running in the same context as my code. So: a) if they can see the cookie, I should be able to see the cookie directly b) if I can't see the cookie, neither should they and they should be broken as well c) the cookie header is a red herring and is just a fallback, they're setting the initial auth state via a different codepath and I need to do the same
s
a) they can't see the cookie you are thinking of, they are creating their own cookie on the server they are running on (in this case
localhost
)
That should answer b and c too
As I've mentioned above
handleCallback
is creating the cookie on your own domain (
localhost
)
a
Ah! That would indeed explain it. I thought
handleCallback
was passing through a cookie received from SB. Here's the meat of `/handlecallBack`:
Copy code
export const handleCallback = (options: HandleCallbackOptions = {}) => {
    const handle: Handle = async ({ event, resolve }) => {
        const req = event.request;
        let res = await resolve(event);

        [...]

        const { event: bodyEvent, session } = await req.json();

        if (bodyEvent === 'SIGNED_IN') {
            if (!session) throw new Error('Auth session missing!');
            setCookies(
                new SvelteKitRequestAdapter(req),
                new SvelteKitResponseAdapter(res),
                [
                    session.access_token ? { key: 'access-token', value: session.access_token }: null,
                ]
            )
        }
    }
}
The function grabs the session from
req.json()
. I'm not sure how this reconciles with your earlier remark that: "No Supabase isn't POSTing anything to the server." ?
s
> The function grabs the session from req.json(). I'm not sure how this reconciles with your earlier remark that: "No Supabase isn't POSTing anything to the server." ? Correct Supabase is not POSTing anything to the server, my client side code is doing this https://github.com/supabase-community/auth-helpers/blob/main/packages/svelte/src/SupaAuthHelper.svelte#L58-L83
a
Huh! So we're ping ponging it: 1) click verify link in email 2) supabase (gotrue) 3) server
/
4) client
/
5) client triggers registered auth listener state change 6) client listener fetches
/callback
(passing in session data somehow, hopefully I can find this in the supabase-js.auth code) 7) server
/callback
sets cookie for my domain as passed by above 8) client (still at
/
) refreshes auth state from
/callback
response
s
Yep pretty much that
a
Thanks for all your help! It looks like step 6 parses the session from the URL hash on the client, which is enforced by the SvelteKit package's auth wrapper around the whole app slot. There's even a note: > // Forward session from client to server where it is set in a Cookie. > // NOTE: this will eventually be removed when the Cookie can be set differently. I think this can be implemented a bit more cleanly. Rough sketch: In global
__layout:load()
, do
if (browser) refreshAuth(url.hash)
, and put the machinery there (parse, set cookie/localStorage, clear hash, update Svelte
$session
store instead of firing event). Should eliminate the need for
/callback
and the application wrapper Svelte component AFAIC.
s
Test that out and see if it works for you, the hash is handled by the
supabase-js
library, so you will have to catch it before the library catches it and removes it form the url
a
I think it'll work. Pro: easier to work with other Svelte code that watches for $session changes. Con: may make using
supabase-js
a lot harder
s
Also remember
load
is going away in SvelteKit at some point in the future
a
I hadn't heard that, if they move to page endpoints only then hopefully they give us a client side hook equivalent / middleware
Or the __section proposal
s
Yeah Rich mentioned it in his talk, one of the last hurdle right now is getting page endpoints working with __layouts
What you outlines above of using the
load
would just be doing the work the
onAuthStateChange
is doing already for you.
Updating the
$session
store on the client doesn't persists the data for the next request. The ping/pong is there for that reason.
I can see you are deep into the library so if you have any improvements to contribute please do so on our GitHub, even if you don't feel like writing code and just want to open an issue with improvements that is most welcomed. https://github.com/supabase-community/auth-helpers
a
Yes, can still set a cookie there if needed for SSR.
The auth-helper is adding several layers of abstraction and wrappers, which is why I'm rolling my own. I would prefer explicit auth handling to the current architecture (withApi(), app wrapper, additional handle functions that masquerade as auth endpoints, nested event listener).
I really appreciate the time you shared helping me sort through this all.
I'll definitely open issues after I'm more deeply familiar with Supabase. The one thing I've seen so far is that I tried to destructure auth:
Copy code
export const {api, signIn, signOut} = supabase.auth
This broke with an error on
this.removeSession()
being undefined I believe, when I called signIn. Guessing the
this
reference is confused by the destructuring. It's minor, and I'm off the happy path already, so it didn't seem worth opening an issue.
s
That wouldn't be an issue with the
auth-helpers
, thats
gotrue-js
thing
a
Just discovered something - GoTrueClient does the majority of what I want automatically:
Copy code
if (settings.detectSessionInUrl && isBrowser() && !!getParameterByName('access_token')) {...}
This is in the client constructor, which
supabase-js
automatically instantiates for you. So, if you create a client instance somewhere on the browser side like
__layout:onMount
, it will process
/#access_token=...
, saving it to localStorage (no cookie set) under
supabase.auth.token
Slightly surprising since this is triggered on an import:
Copy code
// $lib/auth.ts

const supabase = createClient(
    "https://....supabase.co",
    "..."
)

export const provider = supabase.auth
Copy code
// routes/__layout.svelte

<script lang="ts">
    import {provider} from "$lib/auth"
</script>
Hopefully this helps the next person who searches Discord
s
The reason for the cookie is server side use of supabase, I should have made that clear earlier