Fundamentos de seguridad
Por qué los enlaces de email de Supabase redirigen a localhost:3000
Si Supabase redirige a localhost:3000, la Site URL nunca se cambió o tu dirección falta en Redirect URLs. La solución, y el comodín que debes evitar.

En resumen
- Una redirección de Supabase a localhost:3000 viene de la Site URL de la página URL Configuration. Un proyecto empieza con http://localhost:3000, y cada enlace de email vuelve a ella cuando la dirección que pidió tu app no está en la lista Redirect URLs.
- Quien pulsó un enlace de registro suele estar ya confirmado. Supabase confirma la cuenta antes de enviarlo a otra parte, así que puede iniciar sesión en tu sitio real.
- Pon la Site URL en tu dirección en vivo y añade a la lista las direcciones exactas que usas. Un comodín tan amplio que coincide con la app de otra persona deja que un enlace preparado envíe allí a tus usuarios, con la sesión iniciada.
Un usuario nuevo se registra en tu app en vivo, abre el email de confirmación y pulsa el botón. Su navegador abre una página que dice "This site can't be reached" y "localhost refused to connect" (así en un Chrome en inglés), sobre una dirección que empieza así:
http://localhost:3000/#access_token=…&expires_at=…&expires_in=…&refresh_token=…&sb=&token_type=bearer&type=signup
Si tu app usa el flujo PKCE, termina en ?code=…. Construiste la app con
Lovable, Bolt, v0, Cursor, Claude Code o Replit, en la vista previa iba
perfecta, y el inicio de sesión con contraseña sigue funcionando.
Una redirección de Supabase a localhost significa que Supabase Auth, la parte
de Supabase que inicia la sesión de la gente, terminó su trabajo y luego envió
a la persona a la Site URL de tu proyecto, que sigue diciendo
http://localhost:3000. Esa dirección significa "este ordenador". En el
ordenador de tu usuario ahí no hay nada. La solución son dos ajustes en una
página del panel de Supabase.
Esta es la parte que los arreglos rápidos de internet hacen mal: el comodín que hace desaparecer este mensaje en una línea es también el que deja que el enlace de un desconocido envíe a tus usuarios a su página, con la sesión ya iniciada. Los ajustes de abajo hacen que los enlaces funcionen y mantienen esa puerta cerrada.
¿Qué significa una redirección de Supabase a localhost?
Significa que Supabase envió a la persona a la dirección por defecto de tu proyecto, porque no aceptó una mejor.
Cada email que Supabase envía por ti, sea una confirmación de registro, un
magic link, un restablecimiento de contraseña o una invitación, enlaza primero
a la dirección propia de tu proyecto. La
guía de plantillas de email
de Supabase muestra la forma:
https://project-ref.supabase.co/auth/v1/verify?token=…&type=email&redirect_to=https://example.com/path.
Supabase comprueba ahí el token y luego envía el navegador a lo que indique
redirect_to.
Piensa en ello como un poste indicador en la puerta de Supabase. Cada persona
que abre un enlace inicia sesión en la puerta y luego sigue, con la llave en la
mano, por una de las flechas. Las flechas las pones tú. La Site URL es la flecha
que toman todos cuando ninguna otra encaja, y en un proyecto nuevo señala
http://localhost:3000: el taller donde se construyó la app, que en el
ordenador de cualquier otra persona es una habitación vacía.
La
guía de redirecciones
de Supabase dice que la Site URL define la redirección por defecto cuando el
código no indica ningún redirectTo, te pide que la cambies de
http://localhost:3000 a tu dirección de producción y llama a ese ajuste
crítico para las confirmaciones por email y los restablecimientos de
contraseña.
¿Por qué Supabase no hace caso a la dirección que pidió mi app?
Porque esa dirección no está en tu lista Redirect URLs, y Supabase recurre a otra sin avisarte.
Tu app puede indicar dónde debe aterrizar la gente. En la biblioteca de
JavaScript de Supabase eso es emailRedirectTo en signUp y signInWithOtp,
y redirectTo en resetPasswordForEmail y signInWithOAuth. Después Supabase
Auth decide en
una función corta,
en este orden:
- La dirección que pidió tu app, si coincide con una entrada de tu lista Redirect URLs o está en el mismo dominio que tu Site URL.
- La dirección de la página de la que vino la petición, tal como la comunicó el navegador, con la misma regla.
- Si no, la Site URL. Supabase no da ningún error y la consola de tu app no muestra nada.
Toma esa decisión cuando se envía el email, y la dirección elegida entra en el
enlace. Cuando alguien abre el enlace, Supabase vuelve a comprobar esa dirección
con tus ajustes tal como están en ese momento. La propia
respuesta de solución de problemas
de Supabase resume la regla en una frase: para que la opción redirectTo
funcione, tienes que poner la URL exacta en el ajuste Redirect URLs.
El mismo ajuste decide adónde devuelve "Sign in with Google" a la gente, así que un botón de Google que aterriza en localhost tiene la misma causa. Un botón de Google que se para antes, en una página que dice "Unsupported provider: provider is not enabled", es en cambio un interruptor en Supabase y una lista en Google.
Por qué no lo viste antes del lanzamiento
Porque las dos formas en que lo probaste nunca llegan a la Site URL.
- El inicio de sesión con contraseña ocurre dentro de la página. No hay enlace de email ni paso por la puerta de Supabase, así que nadie lee el poste.
- Si construyes con Cursor o Claude Code y tu servidor de desarrollo estaba en marcha en el puerto 3000 cuando probaste el email, el enlace abrió tu propia copia local y pareció funcionar.
- En el backend integrado de Lovable puede que nunca te pase. La
documentación de autenticación
de Lovable dice que pone la Site URL en tu dirección publicada de
lovable.appcuando publicas, y que añade un dominio propio a las redirect URLs cuando lo conectas. La misma página dice que las apps que usan tu propio proyecto de Supabase no reciben actualizaciones automáticas.
¿Está confirmada la cuenta aunque la página no cargara?
Sí, en una confirmación de registro. Supabase confirma primero y envía el navegador a otra parte después.
En el código que procesa el enlace, la cuenta se confirma antes de que salga la redirección. La página que no carga llega cuando el trabajo ya está hecho. Así que dile a la persona que puede iniciar sesión en tu sitio en vivo con la contraseña que eligió. Volver a pulsar el mismo email no sirve: el enlace funciona una vez, y la segunda pulsación responde "Email link is invalid or has expired".
Un magic link o un restablecimiento de contraseña es distinto. Esa sesión fue a localhost y se quedó allí, así que cuando hayas corregido los ajustes la persona necesita un email nuevo.
Cómo arreglar la redirección de Supabase a localhost
Cambia la Site URL y luego añade las demás direcciones a las que envías a la gente. Las dos están en la página URL Configuration, dentro de Authentication, en el panel de Supabase.
Pon la Site URL en tu dirección en vivo, con https:// delante.https://yourapp.com, escrita entera. Sin el https://, Supabase la lee como
una ruta dentro de su propia dirección, y la persona aterriza en tu proyecto de
Supabase con "No API key found in request".
A partir de ahí, cualquier dirección de ese dominio se acepta en cualquier ruta
sin entrada en la lista, así que https://yourapp.com/welcome funciona en
cuanto la Site URL es correcta.
Añade las demás direcciones a Redirect URLs. La versión con www si la
gente la usa, la dirección publicada en el builder
(https://yourapp.lovable.app, por ejemplo) si la gente se registra ahí, y
nada más. Una entrada es un patrón, así que https://yourapp.com/** cubre cada
página de ese único dominio.
Comprueba lo que pide tu código. Si pasa window.location.origin, la
dirección es aquella en la que se abrió la página, y cada dirección que eso
pueda producir tiene que estar en el dominio de tu Site URL o en la lista.
Compáralas letra por letra: con www o sin él, con https o sin él. Una
entrada sin /** al final coincide con esa dirección exacta y con nada por
debajo.
Revisa tus plantillas de email. Una plantilla que construye su propio
enlace con {{ .SiteURL }} envía a todo el mundo a la Site URL, pida lo que
pida tu app. La guía de redirecciones de Supabase dice que, al usar la opción
redirectTo, puede que tengas que sustituir {{ .SiteURL }} por
{{ .RedirectTo }} en tus plantillas de email.
| Lo que ves | Lo que cambias |
|---|---|
Todos los enlaces abren localhost:3000 | La Site URL |
| Los enlaces abren tu Site URL en lugar de la dirección que pidió tu código | Añade esa dirección a Redirect URLs, letra por letra |
| El dominio en vivo funciona y la dirección publicada en el builder no | Añade la dirección publicada a Redirect URLs |
| Un enlace abre tu dirección de Supabase con "No API key found in request" | Añade https:// a la Site URL |
| La dirección que pide tu app está en la lista y el enlace sigue sin hacerle caso | El {{ .SiteURL }} de la plantilla de email |
Después regístrate otra vez con una dirección que no hayas usado. Un email
enviado antes del arreglo con el enlace estándar de Supabase, y abierto dentro
de su hora, aterriza ahora en tu sitio en vivo, porque Supabase vuelve a
comprobar la dirección cuando se abre el enlace. Eso vale salvo que
http://localhost:3000 siga en tu lista.
¿Qué Redirect URLs es seguro permitir?
Las direcciones exactas que son tuyas. Un comodín es para los despliegues de vista previa, y solo limitado a tu propio equipo o proyecto.
La guía de redirecciones de Supabase lo dice claro: el "globstar" (**) es útil
para el desarrollo local y las direcciones de vista previa, pero en producción
recomienda fijar la ruta de redirección exacta de tu Site URL.
El motivo es lo que viaja con la persona. En el flujo por defecto, Supabase
escribe la nueva sesión en la dirección a la que la envía:
el código que la construye
añade #access_token=…&refresh_token=… a la dirección de redirección.
Cualquier página que esté en esa dirección puede leer esos dos valores, y con
ellos es ese usuario. En el poste, la flecha decide quién recibe la llave.
Así que una entrada que coincide con una dirección que controla otra persona es una vía de entrada a las cuentas de tus usuarios. Cualquiera puede pedirle a tu proyecto que envíe por email un enlace de inicio de sesión a uno de tus usuarios, con la clave pública que está en tu app a propósito, e indicar su propia página como destino. El email que llega es de verdad de tu app. El flujo PKCE no cambia esto, porque quien escribió ese enlace empezó el inicio de sesión, y PKCE entrega la sesión a quien lo empezó.
Los patrones que coinciden con direcciones que otras personas pueden crear:
**solo coincide con todas las direcciones que existen.https://*.vercel.app/**coincide con todos los despliegues de Vercel de internet, y cualquiera puede crear uno. El propio ejemplo de Supabase lleva tu equipo:https://*-<team-or-account-slug>.vercel.app/**.https://*.lovable.app/**,https://*.netlify.app/**y la misma forma en el dominio de cualquier builder hacen lo mismo para ese builder. El ejemplo de Supabase para Netlify también está limitado:https://**--my_org.netlify.app/**.
Si acabas de arreglar esto pegando un comodín, vuelve a mirar la lista hoy.
El lanzamiento es también el momento en que se revisan los demás ajustes que solo importan en producción. Nuestro escaneo gratuito lee tu app en vivo desde fuera e informa de lo que un desconocido puede alcanzar: tablas que responden a cualquiera, claves secretas en el código que envía tu app, cabeceras que faltan, el certificado. Tarda unos 20 segundos y no necesita cuenta: escanea tu app.
Vigilar la app en vivo después del lanzamiento
Este ajuste se corrige una vez. Lo que tu app envía a los visitantes cambia con cada despliegue, y esa parte se puede vigilar desde fuera.
Reeve Monitor sigue comprobando tu app en vivo:
- si está disponible, cada 60 segundos, para distinguir "el enlace no funciona" de "el sitio está caído"
- las nueve comprobaciones cada hora, en hasta tres apps, incluidas las claves secretas en el código que tu app envía a los visitantes
- un mensaje cuando un resultado cambia, para que un problema que sale con un despliegue posterior te llegue sin que tengas que ir a mirar
- un informe mensual, en lenguaje claro, de lo que vio
Reeve Care guarda una copia de tu base de datos de Supabase, con las cuentas incluidas:
- una copia cifrada según una programación, en el almacenamiento propio de Reeve en la UE, fuera de tu cuenta de Supabase
- el esquema auth en el que viven las cuentas de tus usuarios, para que vuelvan con todo lo demás
- cada copia verificada antes de contar, contando las filas de cada tabla
- también tus archivos subidos, en cuanto conectes una credencial de Storage
- una restauración en un clic, con una copia de seguridad del estado actual tomada antes
- todo lo que hace Monitor
Qué hacer ahora
Qué hacer
- Pon la Site URL de la página URL Configuration en tu dirección en vivo, con
https://incluido. - Añade a Redirect URLs las demás direcciones exactas a las que envías a la gente, como la versión con
wwwy la dirección publicada en el builder. - Asegúrate de que cada dirección que tu código pasa como
emailRedirectTooredirectToestá en la lista, y usa{{ .RedirectTo }}en las plantillas que deban seguirla. - Di a quien vio la página de localhost después de registrarse que su cuenta está confirmada. Los magic links y los restablecimientos de contraseña necesitan un email nuevo.
- Limita los comodines a las vistas previas de tu equipo, y nunca permitas todas las apps del dominio de un builder.
Si la gente pulsa el enlace nuevo y le dice que ha caducado, ese mensaje tiene su propia página. La lista de seguridad de 10 minutos cubre lo que suele quedar abierto en una app recién lanzada.
Preguntas frecuentes
¿Por qué mi email de confirmación de Supabase abre localhost:3000?
Porque Supabase envió a la persona a la Site URL de tu proyecto, y la Site URL sigue siendo http://localhost:3000. Supabase la usa siempre que la dirección que pidió tu app falta en la lista Redirect URLs, o cuando tu app no pidió ninguna. Cámbiala en la página URL Configuration, dentro de Authentication.
¿Está confirmada la cuenta si el enlace abrió localhost?
Sí, en una confirmación de registro. Supabase confirma la dirección primero y envía el navegador a otra parte después, así que la página que falla llega cuando el trabajo ya está hecho. La persona puede iniciar sesión en tu sitio en vivo con la contraseña que eligió. Un magic link o un restablecimiento de contraseña hay que enviarlo de nuevo.
Añadí mi sitio a Redirect URLs y sigue yendo a localhost. ¿Por qué?
La entrada tiene que coincidir con la dirección que se le ofrece a Supabase, letra por letra: con www o sin él, con https o sin él, y la ruta, porque una entrada sin /** al final solo coincide con esa dirección. Si coinciden y el enlace sigue sin hacerle caso, tu plantilla de email construye su enlace con {{ .SiteURL }}. Y corrige la propia Site URL, porque es a la que vuelve todo lo demás.
¿Tengo que añadir mi Site URL a la lista Redirect URLs?
No. Supabase acepta cualquier dirección del propio dominio de la Site URL sin entrada en la lista, en cualquier ruta. La lista es para las demás direcciones: una versión con www, la dirección publicada en el builder, los despliegues de vista previa.
¿Puedo usar un comodín en Redirect URLs de Supabase?
Para vistas previas, limitado a tu propio equipo o proyecto, como en los ejemplos de Supabase. Nunca uno que coincida con direcciones que otras personas pueden crear, como todas las apps de vercel.app o lovable.app. Supabase recomienda la dirección exacta de tu sitio en producción.