Demo, all content is generated
Question

Magic link login fails only when people open the email on their phone

Solved · 2204 views · asked by renata_builds · edited

My yoga class booking app uses magic link login. On laptop it's perfect. But most of my students request the link on the laptop and then click it in the Gmail app on their phone, or request it on phone in Instagram and open in Gmail. They get sent back to the login page and nothing happens. Sometimes the URL has error_description=...code+verifier... in it.

One student told me she tried 5 times. I'm embarrassed.

What I’ve tried

Checked the redirect URLs in Supabase, they are correct. Asked Lovable to 'make magic link work on mobile' and it added a loading spinner.

Comment
Which Supabase client does the app use, @supabase/ssr or plain supabase-js? Tells me which flow you're on. hannah_reyes · edited
ssr I think, Lovable made it renata_builds · edited

3 answers

Marked as helpful by the asker
hannah_reyes · edited

Your app uses the PKCE flow. When someone asks for a link, the browser stores a secret (the code verifier) locally. The link only works in that same browser. Laptop request → phone click = different browser, no verifier, failure. Gmail's in-app browser counts as a different browser too.

Fix: switch magic links to the token_hash flow, which is verified server-side and doesn't need the verifier.

  1. Supabase → Authentication → Email Templates → Magic Link, change the link to:
<a href="{{ .SiteURL }}/auth/confirm?token_hash={{ .TokenHash }}&type=email">Log in</a>
  1. Add a route /auth/confirm that does:
const { error } = await supabase.auth.verifyOtp({ type: 'email', token_hash })

and redirects to the app on success.

Now the link works in any browser on any device.

Comment
I gave this exact answer to Lovable and it made the confirm page. Tested laptop → phone and it works!! renata_builds · edited
Same bug in my app, same fix. Thanks. hugo_l · edited
mira_dev · edited

Alternative if you want zero routing work: send a 6-digit code instead of a link ({{ .Token }} in the template, verifyOtp({ email, token, type: 'email' }) in the app). People type it on whatever device they're on. For a class booking app with lots of phone users, codes are often less confusing than links.

Comment
Maybe later, my students are used to links now. renata_builds · edited
rafa_dev · edited

Small gotcha with the new template: {{ .SiteURL }} is whatever your Site URL setting says. If that's still localhost (the default), every email points to localhost. Check it before you test.

Comment
Already fixed that one last month the hard way :) renata_builds · edited