Source:
ocean/docs/fix-otp-magic-link-issue.md| ✏️ Edit on GitHub
Fix: OTP vs Magic Link Issue
Problem
The application expects users to receive a 6-digit OTP code via email, but Supabase is sending magic links instead. When users click the magic link, they get redirected with an error: #error=access_denied&error_code=otp_expired&error_description=Email+link+is+invalid+or+has+expired
Root Cause
- Supabase email templates haven't been configured to send OTP codes
- The default templates send magic links using
{{ .ConfirmationURL }} - The redirect URL in the magic link may not be properly configured
Solution
Option 1: Configure Supabase to Send OTP Codes (Recommended)
Follow the instructions in /docs/SETUP_OTP.md to update all email templates in Supabase dashboard to use {{ .Token }} instead of {{ .ConfirmationURL }}.
Option 2: Update Auth Service to Specify emailRedirectTo
If you want to keep using magic links, update the auth service to include the proper redirect URL:
// src/services/auth.ts
const { error } = await supabase.auth.signInWithOtp({
email,
options: {
shouldCreateUser: true,
data: metadata,
emailRedirectTo: `${window.location.origin}/auth/callback`,
},
})
Option 3: Handle Magic Link in Auth Callback
The app already has an auth callback route at /auth/callback that should handle the magic link. The issue might be:
- The hash fragment (#error=...) is being handled incorrectly
- The token might have expired (default is 5 minutes)
Immediate Workaround
For testing, you can:
- Use the Supabase dashboard to manually create users
- Use the local Inbucket email service if running Supabase locally
- Configure the email templates as described in SETUP_OTP.md
Long-term Fix
The application is designed for OTP flow, so configuring Supabase to send OTP codes is the proper solution.