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.

9 min read

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

  1. 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.
  2. A rejected redirectTo does not fail the request. Auth falls back to Site URL and still sends the email.
  3. Tokens ride in the URL fragment after verify. The final Location is a session handoff.
  4. https://*-<team>.vercel.app/** matches every project under that team slug. In our test evil-acme.vercel.app got the token with the docs style pattern for team acme.
  5. Trailing ** without a path boundary is dangerous. https://yourapp.com** matched yourapp.com.evil.com and similar hosts in the unit test.
  6. Prefer exact production URLs. Keep preview wildcards only while you need them, scoped to your team, and remove localhost from 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 redirectTo is 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://**, **, or https://*.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:3000 after the first Lovable or Bolt deploy.
  • Adding https://** or https://*.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 localhost patterns on a production Supabase project and forgetting them.
  • Copying next or redirectTo from the query into signInWithOAuth or signInWithOtp (dashboard allow list or not). Covered in the open redirect guide.
  • Assuming a bad redirectTo will 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://**, no https://*.vercel.app/**, no trailing https://yourdomain.com**
  • localhost patterns 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

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.