Environment:
- Project ref:
scilqibpvkaopnsxyaqs
- Site URL:
https://crew.ridif.app
- Redirect URLs allow-list: both
https://crew.ridif.app/** (wildcard) and https://crew.ridif.app/complete-profile (exact literal) tested, same result either way
Summary:
When requesting a sign-in email via signInWithOtp/POST /auth/v1/otp with options.emailRedirectTo set to a URL containing a path or query string, the resulting email's {{ .ConfirmationURL }} always resolves to the bare Site URL — the path/query is silently dropped — even though the exact same value is present on the Redirect URLs allow-list and is honored correctly when applied directly to /auth/v1/verify.
Reproduction:
POST https://scilqibpvkaopnsxyaqs.supabase.co/auth/v1/otp
{"email":"<test address>","create_user":true,"options":{"email_redirect_to":"https://crew.ridif.app/complete-profile"}}
- Inspect the sent email's link (via a service that exposes raw email source, e.g. Mailinator's LINKS view — not just the rendered button, which can visually mask the actual href). It reads:
https://scilqibpvkaopnsxyaqs.supabase.co/auth/v1/verify?token=<hash>&type=signup&redirect_to=https://crew.ridif.app
— the requested /complete-profile path is gone.
- Repeat for a returning user (confirm the account first, then request sign-in again for the same address) to get a genuine
type=magiclink email rather than type=signup. Same result: redirect_to=https://crew.ridif.app, path stripped. Confirmed via the actual email's type=magiclink value and a distinct subject line, not assumed.
- Decisive isolation step: take the token from step 2 or 3 and manually issue
GET /auth/v1/verify?token=<same token>&type=<signup|magiclink>&redirect_to=https://crew.ridif.app/complete-profile
directly (bypassing the emailed link, supplying redirect_to on the request myself). This succeeds and correctly returns a 303 to https://crew.ridif.app/complete-profile#access_token=... — full path preserved.
Conclusion this points to: the Redirect URLs allow-list matching is correct, and /auth/v1/verify respects it when redirect_to is supplied directly on the request. The bug is isolated to send-time construction of .ConfirmationURL for the outgoing email — that code path isn't applying the same allow-list match (or is applying it incorrectly), and falls back to bare Site URL instead of erroring or matching.
Template used (ruling out a template authoring mistake): our Magic Link template already correctly references {{ .ConfirmationURL }}, not {{ .SiteURL }}:
<h2>Sign in to your Ridif Crew Account</h2>
<p>Click the link below to sign in, or enter this code on the sign-in page.</p>
<p>They work once and expire in 1 hour.</p>
<p><a href="{{ .ConfirmationURL }}">Sign in</a></p>
<p>Code: {{ .Token }}</p>
This rules out the common "template hardcodes .SiteURL" misconfiguration — the template variable itself is populated incorrectly server-side.
Expected: .ConfirmationURL's embedded redirect_to should preserve the full requested path/query whenever it matches an allow-list entry, consistent with how /auth/v1/verify already behaves when the same value is supplied directly.
Current workaround in use: bypassing .ConfirmationURL entirely and constructing the verify link manually in the template using {{ .TokenHash }} + {{ .RedirectTo }} against the known project Auth host, e.g.:
<a href="https://scilqibpvkaopnsxyaqs.supabase.co/auth/v1/verify?token_hash={{ .TokenHash }}&type=magiclink&redirect_to={{ .RedirectTo }}">Sign in</a>
Environment:
scilqibpvkaopnsxyaqshttps://crew.ridif.apphttps://crew.ridif.app/**(wildcard) andhttps://crew.ridif.app/complete-profile(exact literal) tested, same result either waySummary:
When requesting a sign-in email via
signInWithOtp/POST /auth/v1/otpwithoptions.emailRedirectToset to a URL containing a path or query string, the resulting email's{{ .ConfirmationURL }}always resolves to the bare Site URL — the path/query is silently dropped — even though the exact same value is present on the Redirect URLs allow-list and is honored correctly when applied directly to/auth/v1/verify.Reproduction:
POST https://scilqibpvkaopnsxyaqs.supabase.co/auth/v1/otp{"email":"<test address>","create_user":true,"options":{"email_redirect_to":"https://crew.ridif.app/complete-profile"}}/complete-profilepath is gone.type=magiclinkemail rather thantype=signup. Same result:redirect_to=https://crew.ridif.app, path stripped. Confirmed via the actual email'stype=magiclinkvalue and a distinct subject line, not assumed.redirect_toon the request myself). This succeeds and correctly returns a303tohttps://crew.ridif.app/complete-profile#access_token=...— full path preserved.Conclusion this points to: the Redirect URLs allow-list matching is correct, and
/auth/v1/verifyrespects it whenredirect_tois supplied directly on the request. The bug is isolated to send-time construction of.ConfirmationURLfor the outgoing email — that code path isn't applying the same allow-list match (or is applying it incorrectly), and falls back to bare Site URL instead of erroring or matching.Template used (ruling out a template authoring mistake): our Magic Link template already correctly references
{{ .ConfirmationURL }}, not{{ .SiteURL }}:This rules out the common "template hardcodes
.SiteURL" misconfiguration — the template variable itself is populated incorrectly server-side.Expected:
.ConfirmationURL's embeddedredirect_toshould preserve the full requested path/query whenever it matches an allow-list entry, consistent with how/auth/v1/verifyalready behaves when the same value is supplied directly.Current workaround in use: bypassing
.ConfirmationURLentirely and constructing the verify link manually in the template using{{ .TokenHash }}+{{ .RedirectTo }}against the known project Auth host, e.g.: