S
Supabase•8mo ago
Revadike

[Nuxt] Login flow with magic link

For some reason, I can't make the magic link work. I have a nuxt 3 project with @nuxtjs/supabase module. I run supabase start && supabase functions serve I have an edge function login that does the following:
import { createClient } from "jsr:@supabase/supabase-js@2";

Deno.serve(async () => {
const supabase = createClient(
Deno.env.get("SUPABASE_URL") ?? "",
// Deno.env.get("SUPABASE_ANON_KEY") ?? "",
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
);

const email = "email@example.com";

const { data, error } = await supabase.auth.admin.generateLink({
type: "magiclink",
email,
options: {
redirectTo: "http://localhost:3000/confirm",
},
});

console.log({ data, error });

return new Response(
JSON.stringify({
url: `${data?.properties?.action_link}/confirm&email=${
encodeURIComponent(email)
}`,
}),
{ headers: { "Content-Type": "application/json" } },
);
});
import { createClient } from "jsr:@supabase/supabase-js@2";

Deno.serve(async () => {
const supabase = createClient(
Deno.env.get("SUPABASE_URL") ?? "",
// Deno.env.get("SUPABASE_ANON_KEY") ?? "",
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "",
);

const email = "email@example.com";

const { data, error } = await supabase.auth.admin.generateLink({
type: "magiclink",
email,
options: {
redirectTo: "http://localhost:3000/confirm",
},
});

console.log({ data, error });

return new Response(
JSON.stringify({
url: `${data?.properties?.action_link}/confirm&email=${
encodeURIComponent(email)
}`,
}),
{ headers: { "Content-Type": "application/json" } },
);
});
when I generate a magic link and open it on my nuxt 3 application, I don't get logged in automatically. This is what supposed to happen, right? user from useSupabaseUser remains null. Here is confirm.vue:
<script setup>
const user = useSupabaseUser();

watch(() => user.value, () => {
if (user.value) {
navigateTo('/');
}
});
</script>

<template>
<div>
Redirecting...
</div>
</template>
<script setup>
const user = useSupabaseUser();

watch(() => user.value, () => {
if (user.value) {
navigateTo('/');
}
});
</script>

<template>
<div>
Redirecting...
</div>
</template>
I even added this to config.toml:
additional_redirect_urls = ["https://127.0.0.1:3000","http://127.0.0.1:3000/inloggen","http://127.0.0.1:3000/confirm"]
additional_redirect_urls = ["https://127.0.0.1:3000","http://127.0.0.1:3000/inloggen","http://127.0.0.1:3000/confirm"]
40 Replies
DevsrealmGuy
DevsrealmGuy•8mo ago
What is this outputting? console.log({ data, error });
Revadike
RevadikeOP•8mo ago
serving the request with supabase/functions/login
[Info] {
data: {
properties: {
action_link: "http://127.0.0.1:54321/auth/v1/verify?token=c867d8a85f4ac906e43ac6e9f87c640fccfda70282fc4797a60bd969&type=signup&redirect_to=http://127.0.0.1:3000",
email_otp: "603111",
hashed_token: "c867d8a85f4ac906e43ac6e9f87c640fccfda70282fc4797a60bd969",
redirect_to: "http://127.0.0.1:3000",
verification_type: "signup"
},
user: {
id: "f0a15903-5156-4bc2-841b-26a0a656494b",
aud: "authenticated",
role: "authenticated",
email: "email@example.com",
phone: "",
confirmation_sent_at: "2025-01-21T13:40:04.321928229Z",
app_metadata: { provider: "email", providers: [ "email" ] },
user_metadata: {},
identities: [
{
identity_id: "aa733651-c471-4f2b-b641-24aa55facc9a",
id: "f0a15903-5156-4bc2-841b-26a0a656494b",
user_id: "f0a15903-5156-4bc2-841b-26a0a656494b",
identity_data: [Object],
provider: "email",
last_sign_in_at: "2025-01-21T13:40:04.406509545Z",
created_at: "2025-01-21T13:40:04.40656Z",
updated_at: "2025-01-21T13:40:04.40656Z",
email: "email@example.com"
}
],
created_at: "2025-01-21T13:40:04.398016Z",
updated_at: "2025-01-21T13:40:04.408475Z",
is_anonymous: false
}
},
error: null
}
serving the request with supabase/functions/login
[Info] {
data: {
properties: {
action_link: "http://127.0.0.1:54321/auth/v1/verify?token=c867d8a85f4ac906e43ac6e9f87c640fccfda70282fc4797a60bd969&type=signup&redirect_to=http://127.0.0.1:3000",
email_otp: "603111",
hashed_token: "c867d8a85f4ac906e43ac6e9f87c640fccfda70282fc4797a60bd969",
redirect_to: "http://127.0.0.1:3000",
verification_type: "signup"
},
user: {
id: "f0a15903-5156-4bc2-841b-26a0a656494b",
aud: "authenticated",
role: "authenticated",
email: "email@example.com",
phone: "",
confirmation_sent_at: "2025-01-21T13:40:04.321928229Z",
app_metadata: { provider: "email", providers: [ "email" ] },
user_metadata: {},
identities: [
{
identity_id: "aa733651-c471-4f2b-b641-24aa55facc9a",
id: "f0a15903-5156-4bc2-841b-26a0a656494b",
user_id: "f0a15903-5156-4bc2-841b-26a0a656494b",
identity_data: [Object],
provider: "email",
last_sign_in_at: "2025-01-21T13:40:04.406509545Z",
created_at: "2025-01-21T13:40:04.40656Z",
updated_at: "2025-01-21T13:40:04.40656Z",
email: "email@example.com"
}
],
created_at: "2025-01-21T13:40:04.398016Z",
updated_at: "2025-01-21T13:40:04.408475Z",
is_anonymous: false
}
},
error: null
}
and after initial signup:
serving the request with supabase/functions/login
[Info] {
data: {
properties: {
action_link: "http://127.0.0.1:54321/auth/v1/verify?token=6bfaa8d5f2e75f7d0e399a3087757d3d73dace601f2191622276e995&type=magiclink&redirect_to=http://127.0.0.1:3000",
email_otp: "167906",
hashed_token: "6bfaa8d5f2e75f7d0e399a3087757d3d73dace601f2191622276e995",
redirect_to: "http://127.0.0.1:3000",
verification_type: "magiclink"
},
user: {
id: "f0a15903-5156-4bc2-841b-26a0a656494b",
aud: "authenticated",
role: "authenticated",
email: "email@example.com",
email_confirmed_at: "2025-01-21T13:40:10.794146Z",
phone: "",
confirmation_sent_at: "2025-01-21T13:40:04.321928Z",
confirmed_at: "2025-01-21T13:40:10.794146Z",
recovery_sent_at: "2025-01-21T13:40:58.932930986Z",
last_sign_in_at: "2025-01-21T13:40:10.801384Z",
app_metadata: { provider: "email", providers: [ "email" ] },
user_metadata: { email_verified: true },
identities: [
{
identity_id: "aa733651-c471-4f2b-b641-24aa55facc9a",
id: "f0a15903-5156-4bc2-841b-26a0a656494b",
user_id: "f0a15903-5156-4bc2-841b-26a0a656494b",
identity_data: [Object],
provider: "email",
last_sign_in_at: "2025-01-21T13:40:04.406509Z",
created_at: "2025-01-21T13:40:04.40656Z",
updated_at: "2025-01-21T13:40:04.40656Z",
email: "email@example.com"
}
],
created_at: "2025-01-21T13:40:04.398016Z",
updated_at: "2025-01-21T13:40:58.933972Z",
is_anonymous: false
}
},
error: null
}
serving the request with supabase/functions/login
[Info] {
data: {
properties: {
action_link: "http://127.0.0.1:54321/auth/v1/verify?token=6bfaa8d5f2e75f7d0e399a3087757d3d73dace601f2191622276e995&type=magiclink&redirect_to=http://127.0.0.1:3000",
email_otp: "167906",
hashed_token: "6bfaa8d5f2e75f7d0e399a3087757d3d73dace601f2191622276e995",
redirect_to: "http://127.0.0.1:3000",
verification_type: "magiclink"
},
user: {
id: "f0a15903-5156-4bc2-841b-26a0a656494b",
aud: "authenticated",
role: "authenticated",
email: "email@example.com",
email_confirmed_at: "2025-01-21T13:40:10.794146Z",
phone: "",
confirmation_sent_at: "2025-01-21T13:40:04.321928Z",
confirmed_at: "2025-01-21T13:40:10.794146Z",
recovery_sent_at: "2025-01-21T13:40:58.932930986Z",
last_sign_in_at: "2025-01-21T13:40:10.801384Z",
app_metadata: { provider: "email", providers: [ "email" ] },
user_metadata: { email_verified: true },
identities: [
{
identity_id: "aa733651-c471-4f2b-b641-24aa55facc9a",
id: "f0a15903-5156-4bc2-841b-26a0a656494b",
user_id: "f0a15903-5156-4bc2-841b-26a0a656494b",
identity_data: [Object],
provider: "email",
last_sign_in_at: "2025-01-21T13:40:04.406509Z",
created_at: "2025-01-21T13:40:04.40656Z",
updated_at: "2025-01-21T13:40:04.40656Z",
email: "email@example.com"
}
],
created_at: "2025-01-21T13:40:04.398016Z",
updated_at: "2025-01-21T13:40:58.933972Z",
is_anonymous: false
}
},
error: null
}
if u want to take a look, I can set up a minimal reproduction repo
garyaustin
garyaustin•8mo ago
Also remember Nuxt did their own shell on top of Supabase if they have any special requirements for this process.
Revadike
RevadikeOP•8mo ago
Nuxt x Supabase - Docs
Get Started - Nuxt x Supabase - Docs
@nuxtjs/supabase is a Nuxt module for first class integration with Supabase.
Revadike
RevadikeOP•8mo ago
?
DevsrealmGuy
DevsrealmGuy•8mo ago
Please do, curious to take a look Ouch, gotta go now, hopefully garry can help you out
garyaustin
garyaustin•8mo ago
Yes that is done by Nuxt not Supabase. Just pointing out that they Could have different process that Supabase docs, not that they do. I don't know I've never used it. I've seen though here there are differences.
Revadike
RevadikeOP•8mo ago
well the official supabase docs use it too and link to a demo using it which is outdated btw
Revadike
RevadikeOP•8mo ago
GitHub
Update Nuxt 3 User Management example by Shooteger · Pull Request #...
Updating package versions, as well as .lock file. Adding .env-example file and set runtimeConfig with supabase key I have read the CONTRIBUTING.md file. YES What kind of change does this PR introdu...
Revadike
RevadikeOP•8mo ago
I checked this, but it did not help
Revadike
RevadikeOP•8mo ago
Revadike
RevadikeOP•8mo ago
however his suggested fix did nothing for me sadly man, it's been a rough start trying out supabase...
garyaustin
garyaustin•8mo ago
Nuxt I think uses cookies, supabase-js in Javascript plain does not. Supabase has its own cookie based server/browser setup called SSR. But one would think their guide from the Nuxt site would work at least.
Revadike
RevadikeOP•8mo ago
GitHub
GitHub - Revadike/nuxt3-supabase
Contribute to Revadike/nuxt3-supabase development by creating an account on GitHub.
Revadike
RevadikeOP•8mo ago
^ try please
garyaustin
garyaustin•8mo ago
If this is a Nuxt specific issue you might add Nuxt to the title. As there are some Nuxt users here.
Revadike
RevadikeOP•8mo ago
how does one login with a magic link token with supabase-js? does it also automatically refresh session?
garyaustin
garyaustin•8mo ago
When you click the link it calls Supabase to confirm the user email and then redirects back to the browser. A user token and session info is provided (after a #) and that is extracted by the supabase client in a browser and then the session is valid and stored in local storage. From that point on it is refreshed before it expires. Note the token can't be read from a server as it is after a # which is why I expect turning off SSR is required for Nuxt as it won't work server side.
Revadike
RevadikeOP•8mo ago
makes sense well I tried the manual method with supabase-js I tried both the hashed_token and otp token method (created by supabase.auth.admin.generateLink in the edge function), using supabase.auth.verifyOtp in the client for both it returns the code or email link is invalid or expired
Revadike
RevadikeOP•8mo ago
No description
Revadike
RevadikeOP•8mo ago
this could be the underlying issue why @nuxtjs/supabase won't work for me they probably try the same thing but I still don't know why here is my current nuxt config
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: "2024-11-01",
devtools: { enabled: true },
modules: ["@nuxtjs/supabase"],
ssr: false,

runtimeConfig: {
public: {
baseUrl: process.env.BASE_URL || "http://localhost:3000",
},
},

supabase: {
public: {
SUPABASE_URL: process.env.SUPABASE_URL,
SUPABASE_KEY: process.env.SUPABASE_KEY,
},
redirectOptions: {
login: '/',
// callback: '/confirm',
// include: undefined,
// exclude: [],
// cookieRedirect: false,
}
},
});
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: "2024-11-01",
devtools: { enabled: true },
modules: ["@nuxtjs/supabase"],
ssr: false,

runtimeConfig: {
public: {
baseUrl: process.env.BASE_URL || "http://localhost:3000",
},
},

supabase: {
public: {
SUPABASE_URL: process.env.SUPABASE_URL,
SUPABASE_KEY: process.env.SUPABASE_KEY,
},
redirectOptions: {
login: '/',
// callback: '/confirm',
// include: undefined,
// exclude: [],
// cookieRedirect: false,
}
},
});
garyaustin
garyaustin•8mo ago
Check the auth logs on your instanced to see if you get more than one /verify request. If it is called twice the link is no longer any good.
Revadike
RevadikeOP•8mo ago
same thing when trying to use setSession on the client using the access_token and refresh_token returned by visiting the magic link:
No description
garyaustin
garyaustin•8mo ago
Are you using the same instance to generate the link and verify it? Your edge function is running on the instance that deployed it.
Revadike
RevadikeOP•8mo ago
how can I check? i only have 1 supabase project
garyaustin
garyaustin•8mo ago
Are you doing local dev?
Revadike
RevadikeOP•8mo ago
yes supabase start nuxt dev
garyaustin
garyaustin•8mo ago
Your function might be running there and not on the real instance.
Revadike
RevadikeOP•8mo ago
wdym
garyaustin
garyaustin•8mo ago
Log out your environment variables and check the function log.
Revadike
RevadikeOP•8mo ago
No description
garyaustin
garyaustin•8mo ago
If you are doing local dev then it has it's own instance running with its own jwt secret and keys. If your function is on hosted supabase instance then your link is signed by it. If you run than then on your local instance that would generate the error you see, or visa versa.
Revadike
RevadikeOP•8mo ago
ok, what .env values should I set to use my local instance? change SUPABASE_KEY to use the local anon key maybe?
Revadike
RevadikeOP•8mo ago
🎉 🎉 🎉 🎉
No description
garyaustin
garyaustin•8mo ago
If you are doing functions serve then the function is running locally on your local instance. You would need your nuxt code to be using that local url and key. Or you need to deploy the function on your hosted instance and run that.
Revadike
RevadikeOP•8mo ago
i figured it out API_URL as SUPABASE_URL and anon key AS SUPABASE_KEY it makes sense, I just expected supabase would use my local instance by default or something
garyaustin
garyaustin•8mo ago
Your calls will use whatever URL/key you provide to createClient.
Revadike
RevadikeOP•8mo ago
the nuxtjs library creates the client instance for me so i had no idea btw, session is stored in a cookie, not localStorage from what I can tell
garyaustin
garyaustin•8mo ago
Yes that is a Nuxt thing. It is a shell around Supabase-js and sends the local storage calls to a cookie handler I assume (like Supabase SSR does).
Revadike
RevadikeOP•8mo ago
i read it indeed uses supabase ssr under the hood

Did you find this page helpful?