HomeGuidesSupabase Auth redirect URLs: wildcards, tested
Guide
Supabase Auth redirect URLs: wildcards, tested
Supabase Redirect URLs wildcards can send magic link tokens to any matching host. We tested Site URL, allow list patterns, Vercel previews and silent fallback.
Independent notes from VibeLocked. Not a pentest, and not affiliated with the tools named below.
You paste a Vercel preview pattern into Supabase Auth so magic links work on every branch. A week later a teammate ships another project on the same Vercel team. Someone requests a password reset with redirectTo pointed at that other preview URL. The email looks normal. The tokens land on a host you did not expect.
Supabase Auth checks every redirectTo (and the email flow's emailRedirectTo) against Site URL and the Redirect URLs allow list. Wildcards are documented for Netlify and Vercel previews. This guide shows what those patterns actually match, what happens when a value is rejected, and how to set the list without handing session tokens to the wrong host. For the separate bug where your own app copies a query param into a redirect, see open redirects in Auth.js, Supabase, and Clerk.
Tested on October 28, 2026. We ran the open source Supabase Auth server from source (commit c3348e8 on master, October 21, 2026) with its 76 migrations on PostgreSQL 17.11, @supabase/supabase-js 2.118.0, and a local mail catcher. Redirect matching was exercised two ways: the real /verify flow after magic link and password reset emails, and Auth's own IsRedirectURLValid helper with the same gobwas/glob separators Auth uses (. and /). We did not test on a hosted Supabase project. Every claim below is either something we reproduced or a quote from the docs and source linked at the end. VibeLocked is independent and not affiliated with Supabase.
The short version
- Site URL is an allow list of its own. Same scheme, host and port: any path is accepted, even if it is not listed under Redirect URLs.
- A rejected
redirectTodoes not fail the request. Auth falls back to Site URL and still sends the email. - Tokens ride in the URL fragment after verify. The final Location is a session handoff.
https://*-<team>.vercel.app/**matches every project under that team slug. In our testevil-acme.vercel.appgot the token with the docs style pattern for teamacme.- Trailing
**without a path boundary is dangerous.https://yourapp.com**matchedyourapp.com.evil.comand similar hosts in the unit test. - Prefer exact production URLs. Keep preview wildcards only while you need them, scoped to your team, and remove
localhostfrom production.
What Supabase documents
The Redirect URLs guide says the URL in redirectTo should match the Redirect URLs list, and that Site URL "defines the default redirect URL when no redirectTo is specified." It documents wildcards, including * (non-separator characters) and ** (any sequence of characters), and recommends exact production paths:
While the "globstar" (
**) is useful for local development and preview URLs, we recommend setting the exact redirect URL path for your site URL in production.
For Vercel previews the same page shows:
http://localhost:3000/**
https://*-<team-or-account-slug>.vercel.app/**
Netlify's example uses https://**--my_org.netlify.app/**. Auth compiles each allow list entry with gobwas/glob using . and / as separators (see internal/conf/configuration.go in supabase/auth). That is why * stops at a dot or slash and ** can cross them.
What we tested
Config for the live Auth run:
| Setting | Value |
|---|---|
| Site URL | https://yourapp.test |
| Allow list | http://localhost:3000/**, https://*-acme.vercel.app/** |
| Flows | signInWithOtp (magic link) and resetPasswordForEmail |
| Client | supabase-js 2.118.0 against local Auth |
For each case we requested the email, opened the ConfirmationURL from the message (default templates use that link, which already embeds redirect_to), and read the Location header on the 303 from GET /verify without following it.
Live results: where verify sent the session
redirectTo asked |
Final Location host (after verify) |
|---|---|
| omitted | https://yourapp.test (Site URL) |
https://yourapp.test/auth/callback |
same path kept |
https://yourapp.test/deep/path |
same path kept |
https://evil.com/steal |
fell back to https://yourapp.test |
https://myapp-git-feature-acme.vercel.app/auth/callback |
preview URL kept |
https://evil-acme.vercel.app/steal |
attacker style preview kept |
http://localhost:3000/auth/callback |
localhost kept |
https://yourapp.com/x |
fell back to Site URL |
https://yourapp.test.evil.com/ |
fell back |
https://yourapp.test@evil.com/ |
fell back |
https://x-acme.vercel.app (no path) |
fell back (/** did not match) |
Password reset repeated the important rows: https://evil.com/ fell back to Site URL, a matching Vercel preview kept its URL, and https://yourapp.test/reset kept its path.
Every successful verify responded 303 with access_token, refresh_token, expires_in and related fields in the URL fragment. The browser would hand that hash to JavaScript on the destination origin. A matching allow list entry is not a mild open redirect. It is a session delivery.
The client never received an error for a bad redirectTo. signInWithOtp and resetPasswordForEmail both returned success while Auth quietly substituted Site URL. For the rest of the reset flow (enumeration through the 429, scanners that use up the link, and sessions after a reset), see Supabase password reset security, tested. Email change links behave the same way: updateUser({ email }, { emailRedirectTo }) fell back to Site URL for a host outside the list, and the final confirmation link delivered a session (Supabase email change security).
Site URL is broader than it looks
IsRedirectURLValid first compares scheme, hostname and port to Site URL. On a match it returns true without consulting the allow list. Port checks are skipped for loopback hosts. That is why every path under https://yourapp.test worked in our test, and why leaving Site URL at http://localhost:3000 in production sends default redirects to a developer machine URL.
Practical rules:
- Set Site URL to the exact production origin users should land on when
redirectTois omitted or rejected. - Do not put a staging host in Site URL if production emails can be triggered against that project.
- Remember that any path on that origin is allowed, so an open redirect or XSS on your own site can still collect the hash.
Wildcard patterns that over matched
Unit tests called the same IsRedirectURLValid helper Auth uses in mail and verify. Pattern on the left, URL on the right, result in our run:
| Pattern | URL | Allowed |
|---|---|---|
https://*-acme.vercel.app/** |
https://myapp-git-feature-acme.vercel.app/auth/callback |
yes |
https://*-acme.vercel.app/** |
https://evil-acme.vercel.app/auth/callback |
yes |
https://*-acme.vercel.app/** |
https://x-acme.vercel.app.evil.com/ |
no |
https://*.vercel.app/** |
https://attacker-app.vercel.app/steal |
yes |
https://**.vercel.app/** |
https://evil.com/.vercel.app/ |
yes |
https://yourapp.com** |
https://yourapp.com.evil.com/ |
yes |
https://yourapp.com** |
https://yourapp.com@evil.com/ |
yes |
https://yourapp.com* |
https://yourapp.com/auth/callback |
yes |
https://yourapp.com* |
https://yourapp.com.evil.com/ |
no |
https://** |
https://evil.com/steal |
yes |
** |
https://evil.com/steal |
yes |
exact .../auth/callback |
same URL with ?next=/x |
no (query is part of the match string; only #fragment is stripped) |
exact .../auth/callback |
same URL with #x |
yes |
Takeaways we will stick to in production:
- Never add
https://**,**, orhttps://*.vercel.app/**. - Do not hang
**directly on your apex (https://yourapp.com**). Put a/before path wildcards, or use an exact path. - The docs Vercel form
https://*-<team>.vercel.app/**is for your team. Treat every Vercel project under that slug as able to receive Auth redirects. Pair it with Deployment Protection so random visitors cannot open those previews even if a link leaks. - Exact production callback URLs beat wildcards. Add preview patterns only for the environments that need email or OAuth, then remove them.
Email templates and {{ .RedirectTo }}
Default Auth templates link to Auth's own /verify URL and pass redirect_to as a query param. The Redirect URLs guide also shows custom templates that build a link from {{ .RedirectTo }} plus a path and token_hash. That variable is the already validated referrer (or Site URL). If you change templates, keep using the documented variables. Do not build the email link from a raw query string your app received.
OAuth signInWithOAuth({ options: { redirectTo } }) uses the same allow list. We did not stand up a social provider in this test. The matcher is shared.
Common AI generated mistakes
- Leaving Site URL at
http://localhost:3000after the first Lovable or Bolt deploy. - Adding
https://**orhttps://*.vercel.app/**"so previews work." - Copying the docs Vercel line with the wrong team slug, or leaving it in place after the team grows.
- Putting
localhostpatterns on a production Supabase project and forgetting them. - Copying
nextorredirectTofrom the query intosignInWithOAuthorsignInWithOtp(dashboard allow list or not). Covered in the open redirect guide. - Assuming a bad
redirectTowill surface as an error in the client. In our test it did not.
Checklist
- Site URL is the production origin (scheme, host, port)
- Production Redirect URLs are exact callback paths you own
- Preview wildcards, if any, use your real Vercel team or Netlify org slug only
- No
https://**, nohttps://*.vercel.app/**, no trailinghttps://yourdomain.com** -
localhostpatterns removed from the production project (keep them on a separate staging project) - Vercel Deployment Protection on for previews that can receive Auth redirects
- App code never copies an untrusted query param into
redirectTo/emailRedirectTo - Email templates use
{{ .ConfirmationURL }}or documented{{ .RedirectTo }}, not a raw request URL - Magic link and password reset tested with an evil
redirectTo(expect Site URL fallback) and with your real callback
Copy-paste prompt for your AI coding tool
Open this Supabase project's Auth URL Configuration (or config.toml for local). List Site URL and every Redirect URLs entry. Flag https://, https://*.vercel.app/, patterns that end with yourdomain.com**, and any localhost entry on a production project. For Vercel preview patterns, confirm the team slug matches only our team and note that every project under that slug can receive magic link and recovery tokens. Then search the app for redirectTo, emailRedirectTo, redirect_to and next passed into supabase.auth methods. Replace any value built from the query string with a fixed allow listed URL. Show the planned allow list and code edits before applying them. Do not print or ask for any keys.
More prompts: security review prompt for AI coding tools. Broader Auth dashboard pass: Supabase Auth security settings.
Check what your live app exposes with LeakCheck
LeakCheck scans any live app URL or public GitHub repo, read only. On a live Supabase app it finds the publishable key, checks whether that key can read tables, and reads public Auth settings such as whether email signup confirmation is off. It does not log in, request a magic link, or evaluate your Redirect URLs allow list. The Site URL and wildcard review above is something you run in the dashboard (or against your own self hosted Auth config).
Sources
- Supabase: Redirect URLs (Site URL, wildcards, Vercel and Netlify examples, email templates)
- Supabase: Email templates
- Supabase JavaScript reference: signInWithOtp (emailRedirectTo)
- Supabase JavaScript reference: resetPasswordForEmail (redirectTo)
- Supabase JavaScript reference: signInWithOAuth (redirectTo)
- Vercel: System environment variables (VERCEL_URL)
- gobwas/glob (wildcard library Auth compiles allow list entries with)
- Supabase Auth source: IsRedirectURLValid (Site URL match, allow list, fragment strip)
- Supabase Auth source: URIAllowListMap compile (gobwas/glob separators . and /)
- Supabase Auth source: verify GET redirect (RedirectTo, Location)
- Supabase Auth source: default magic link and recovery email templates
Questions
Does Supabase reject a bad redirectTo with an error?
No. In our test a magic link or password reset with redirectTo set to https://evil.com still returned success. Auth fell back to the Site URL and put the session tokens on that URL after verify. The client never saw a redirect error.
Does Site URL alone allow every path on my domain?
Yes for the same scheme, host and port. In our test, with Site URL https://yourapp.test and no matching allow list entry, https://yourapp.test/auth/callback and https://yourapp.test/deep/path both worked. A different host, scheme or port did not.
Is the docs Vercel preview pattern safe on a shared team?
It matches every hostname that ends with your team slug. With GOTRUE_URI_ALLOW_LIST set to https://*-acme.vercel.app/**, both our real preview URL and https://evil-acme.vercel.app/steal received the access token. Anyone who can ship a Vercel project under that team slug can catch magic link and reset tokens.
Which wildcard patterns are unsafe?
In our unit test against Auth's matcher, https://** and a bare ** matched https://evil.com. A trailing https://yourapp.com** matched yourapp.com.evil.com, yourapp.com@evil.com and yourapp.comevil.com. https://**.vercel.app/** matched https://evil.com/.vercel.app/. Prefer exact production URLs, and scope preview patterns to your own team slug only while you need them.
Do password reset links use the same allow list?
Yes. In our test resetPasswordForEmail used the same IsRedirectURLValid check as signInWithOtp. An evil host fell back to Site URL. A matching Vercel preview pattern and a path on the Site URL host both kept the redirect.
Where do the tokens land after the email link?
On a successful verify GET, Auth responded 303 with a Location that put access_token, refresh_token and related fields in the URL fragment. Whoever controls that final URL can read the session from the hash. That is why a loose allow list is a session leak, not only a phishing hop.