Pular para o conteúdo

Noções de segurança

Por que o Supabase redireciona links de e-mail para localhost:3000

Se o Supabase redireciona para localhost:3000, a Site URL nunca foi trocada ou o seu endereço falta em Redirect URLs. A solução, e o curinga a evitar.

Vlad Tkachenko11 min de leitura
Uma placa de sinalização cuja seta acesa aponta para longe da casa aonde deveria levar, na direção de um notebook com a tela apagada.

Em resumo

  • Um redirecionamento do Supabase para localhost:3000 vem da Site URL da página URL Configuration. Um projeto começa com http://localhost:3000, e todo link de e-mail volta para ela quando o endereço que o seu app pediu não está na lista Redirect URLs.
  • Quem apertou um link de cadastro normalmente já está confirmado. O Supabase confirma a conta antes de mandar a pessoa adiante, então ela pode entrar no seu site de verdade.
  • Coloque a Site URL no seu endereço no ar e liste os endereços exatos que você usa. Um curinga tão amplo que cobre o app de outra pessoa deixa um link forjado mandar os seus usuários para lá, já com login feito.

Um usuário novo se cadastra no seu app no ar, abre o e-mail de confirmação e aperta o botão. O navegador dele abre uma página que diz "This site can't be reached" e "localhost refused to connect" (assim num Chrome em inglês), acima de um endereço que começa assim:

http://localhost:3000/#access_token=…&expires_at=…&expires_in=…&refresh_token=…&sb=&token_type=bearer&type=signup

Se o seu app usa o fluxo PKCE, ele termina em ?code=…. Você construiu o app no Lovable, Bolt, v0, Cursor, Claude Code ou Replit, na pré-visualização ele funcionava perfeitamente, e o login com senha continua funcionando.

Um redirecionamento do Supabase para localhost significa que o Supabase Auth, a parte do Supabase que faz o login das pessoas, terminou o trabalho dele e depois mandou a pessoa para a Site URL do seu projeto, que ainda diz http://localhost:3000. Esse endereço quer dizer "este computador". No computador do seu usuário não tem nada ali. A solução são duas configurações numa página do painel do Supabase.

Esta é a parte que as soluções rápidas da internet erram: o curinga que faz essa mensagem sumir com uma linha é também o que deixa o link de um estranho mandar os seus usuários para a página dele, já com login feito. As configurações abaixo fazem os links funcionarem e mantêm essa porta fechada.

O que significa um redirecionamento do Supabase para localhost?

Significa que o Supabase mandou a pessoa para o endereço padrão do seu projeto, porque não aceitou um melhor.

Todo e-mail que o Supabase envia por você, seja uma confirmação de cadastro, um magic link, uma redefinição de senha ou um convite, leva primeiro ao endereço do seu próprio projeto. O guia de modelos de e-mail do Supabase mostra o formato: https://project-ref.supabase.co/auth/v1/verify?token=…&type=email&redirect_to=https://example.com/path. Ali o Supabase verifica o token e depois manda o navegador para o que redirect_to indicar.

Pense nisso como uma placa de sinalização na porta do Supabase. Cada pessoa que abre um link faz login na porta e depois segue, com a chave na mão, por uma das setas. Quem coloca as setas é você. A Site URL é a seta que todo mundo pega quando nenhuma outra serve, e num projeto novo ela aponta para http://localhost:3000: a oficina onde o app foi construído, que na máquina de qualquer outra pessoa é uma sala vazia.

O guia de redirecionamentos do Supabase diz que a Site URL define o redirecionamento padrão quando o código não indica nenhum redirectTo, pede que você a troque de http://localhost:3000 para o seu endereço de produção e chama essa configuração de crítica para confirmações por e-mail e redefinições de senha.

Por que o Supabase ignora o endereço que o meu app pediu?

Porque esse endereço não está na sua lista Redirect URLs, e o Supabase recorre a outro sem avisar você.

O seu app pode dizer onde as pessoas devem chegar. Na biblioteca JavaScript do Supabase isso é emailRedirectTo em signUp e signInWithOtp, e redirectTo em resetPasswordForEmail e signInWithOAuth. Depois o Supabase Auth decide em uma função curta, nesta ordem:

  1. O endereço que o seu app pediu, se ele bate com uma entrada da sua lista Redirect URLs ou fica no mesmo domínio da sua Site URL.
  2. O endereço da página de onde veio a requisição, do jeito que o navegador informou, pela mesma regra.
  3. Senão, a Site URL. O Supabase não dá erro nenhum, e o console do seu app não mostra nada.

Ele toma essa decisão quando o e-mail é enviado, e o endereço escolhido entra no link. Quando alguém abre o link, o Supabase confere esse endereço de novo com as suas configurações do jeito que estão naquele momento. A própria resposta de solução de problemas do Supabase resume a regra numa frase: para a opção redirectTo funcionar, você precisa colocar a URL exata na configuração Redirect URLs.

O Supabase tenta o endereço que o seu app pediu, depois a página de onde veio a requisição. Quando nenhum dos dois está na sua lista, quem decide é a Site URL.

A mesma configuração decide para onde o "Sign in with Google" devolve as pessoas, então um botão do Google que cai em localhost tem a mesma causa. Um botão do Google que para antes, numa página que diz "Unsupported provider: provider is not enabled", é outra coisa: uma chave no Supabase e uma lista no Google.

Por que você não viu isso antes do lançamento

Porque os dois jeitos com que você testou nunca chegam à Site URL.

  • O login com senha acontece dentro da página. Não tem link de e-mail nem passagem pela porta do Supabase, então ninguém lê a placa.
  • Se você constrói com Cursor ou Claude Code e o seu servidor de desenvolvimento estava rodando na porta 3000 quando você testou o e-mail, o link abriu a sua própria cópia local e pareceu funcionar.
  • No backend integrado do Lovable talvez isso nunca aconteça com você. A documentação de autenticação do Lovable diz que ele coloca a Site URL no seu endereço publicado em lovable.app quando você publica, e que adiciona um domínio próprio às redirect URLs quando você o conecta. A mesma página diz que os apps que usam o seu próprio projeto do Supabase não recebem atualizações automáticas.

A conta está confirmada mesmo a página não tendo carregado?

Sim, numa confirmação de cadastro. O Supabase confirma primeiro e só depois manda o navegador adiante.

No código que trata o link, a conta é confirmada antes de o redirecionamento sair. A página que não carrega chega com o trabalho já feito. Então diga à pessoa que ela pode entrar no seu site no ar com a senha que escolheu. Apertar o mesmo e-mail de novo não adianta: o link funciona uma vez, e o segundo aperto responde "Email link is invalid or has expired".

Um magic link ou uma redefinição de senha é diferente. Esse login foi para localhost e ficou lá, então depois que você corrigir as configurações a pessoa precisa de um e-mail novo.

Como corrigir o redirecionamento do Supabase para localhost

Troque a Site URL e depois liste os outros endereços para onde você manda as pessoas. Os dois ficam na página URL Configuration, em Authentication, no painel do Supabase.

Coloque a Site URL no seu endereço no ar, com https:// na frente.https://yourapp.com, escrito por inteiro. Sem o https://, o Supabase lê o endereço como um caminho dentro do próprio endereço dele, e a pessoa cai no seu projeto do Supabase com "No API key found in request". A partir daí qualquer endereço desse domínio é aceito em qualquer caminho sem entrada na lista, então https://yourapp.com/welcome funciona assim que a Site URL estiver certa.

Adicione os outros endereços em Redirect URLs. A versão com www se as pessoas a usam, o endereço publicado no builder (https://yourapp.lovable.app, por exemplo) se as pessoas se cadastram ali, e nada mais. Uma entrada é um padrão, então https://yourapp.com/** cobre todas as páginas desse único domínio.

Confira o que o seu código pede. Se ele passa window.location.origin, o endereço é aquele em que a página foi aberta, e todo endereço que isso possa gerar tem que estar no domínio da sua Site URL ou na lista. Compare letra por letra: com www ou sem, com https ou sem. Uma entrada sem /** no final bate com aquele endereço exato e com nada abaixo dele.

Confira os seus modelos de e-mail. Um modelo que monta o próprio link com {{ .SiteURL }} manda todo mundo para a Site URL, não importa o que o seu app pediu. O guia de redirecionamentos do Supabase diz que, ao usar a opção redirectTo, talvez você precise substituir {{ .SiteURL }} por {{ .RedirectTo }} nos seus modelos de e-mail.

O que você vêO que mudar
Todo link abre localhost:3000A Site URL
Os links abrem a sua Site URL em vez do endereço que o seu código pediuAdicione esse endereço em Redirect URLs, letra por letra
O domínio no ar funciona e o endereço publicado no builder nãoAdicione o endereço publicado em Redirect URLs
Um link abre o seu endereço do Supabase com "No API key found in request"Coloque https:// na Site URL
O endereço que o seu app pede está na lista e o link continua ignorandoO {{ .SiteURL }} do modelo de e-mail

Depois cadastre-se de novo com um endereço que você ainda não usou. Um e-mail enviado antes da correção com o link padrão do Supabase, e aberto dentro da hora dele, agora chega ao seu site no ar, porque o Supabase confere o endereço de novo quando o link é aberto. Isso vale a menos que http://localhost:3000 continue na sua lista.

Quais Redirect URLs é seguro permitir?

Os endereços exatos que são seus. Um curinga serve para deploys de pré-visualização, e só limitado ao seu próprio time ou projeto.

O guia de redirecionamentos do Supabase diz isso com clareza: o "globstar" (**) é útil para desenvolvimento local e endereços de pré-visualização, mas em produção ele recomenda definir o caminho de redirecionamento exato da sua Site URL.

O motivo é o que viaja junto com a pessoa. No fluxo padrão, o Supabase escreve a nova sessão no endereço para onde a manda: o código que monta isso acrescenta #access_token=…&refresh_token=… ao endereço de redirecionamento. Qualquer página nesse endereço pode ler esses dois valores, e com eles ela é aquele usuário. Na placa, é a seta que decide quem recebe a chave.

Então uma entrada que cobre um endereço controlado por outra pessoa é um caminho para dentro das contas dos seus usuários. Qualquer um pode pedir ao seu projeto que mande por e-mail um link de login para um dos seus usuários, usando a chave pública que está no seu app de propósito, e indicar a própria página como destino. O e-mail que chega é mesmo do seu app. O fluxo PKCE não muda isso, porque quem escreveu esse link começou o login, e o PKCE entrega a sessão a quem começou.

Os padrões que cobrem endereços que outras pessoas podem criar:

  • ** sozinho cobre todos os endereços que existem.
  • https://*.vercel.app/** cobre todos os deploys da Vercel na internet, e qualquer um pode criar um. O próprio exemplo do Supabase leva o seu time: https://*-<team-or-account-slug>.vercel.app/**.
  • https://*.lovable.app/**, https://*.netlify.app/** e a mesma forma no domínio de qualquer builder fazem o mesmo para esse builder. O exemplo do Supabase para a Netlify também é limitado: https://**--my_org.netlify.app/**.
Uma entrada exata manda um link forjado para casa. Um curinga que cobre os apps dos outros deixa ele entregar o login a eles.

Se você acabou de resolver isso colando um curinga, olhe a lista de novo hoje.

O lançamento também é a hora em que as outras configurações que só importam em produção são olhadas. O nosso scan gratuito lê o seu app no ar de fora e mostra o que um estranho consegue alcançar: tabelas que respondem a qualquer um, chaves secretas no código que o seu app envia, cabeçalhos que faltam, o certificado. Leva uns 20 segundos e não precisa de conta: escaneie o seu app.

Acompanhar o app no ar depois do lançamento

Essa configuração se corrige uma vez. O que o seu app envia aos visitantes muda a cada deploy, e essa parte dá para vigiar de fora.

Reeve Monitor continua verificando o seu app no ar:

  • se ele está no ar, a cada 60 segundos, para separar "o link não funciona" de "o site caiu"
  • as nove verificações a cada hora, em até três apps, incluindo chaves secretas no código que o seu app envia aos visitantes
  • uma mensagem quando um resultado muda, para que um problema que sai num deploy posterior chegue até você sem você precisar ir olhar
  • um relatório mensal, em linguagem simples, do que ele viu

Reeve Care guarda uma cópia do seu banco de dados Supabase, contas incluídas:

  • uma cópia criptografada em uma programação, no armazenamento próprio da Reeve na UE, fora da sua conta do Supabase
  • o schema auth onde vivem as contas dos seus usuários, para que elas voltem com todo o resto
  • cada cópia verificada antes de contar, contando as linhas de cada tabela
  • seus arquivos enviados também, assim que você conectar uma credencial do Storage
  • uma restauração com um clique, com uma cópia de segurança do estado atual feita antes
  • tudo o que o Monitor faz

O que fazer agora

O que fazer

  • Coloque a Site URL da página URL Configuration no seu endereço no ar, com https://.
  • Adicione em Redirect URLs os outros endereços exatos para onde você manda as pessoas, como a versão com www e o endereço publicado no builder.
  • Garanta que todo endereço que o seu código passa como emailRedirectTo ou redirectTo está na lista, e use {{ .RedirectTo }} nos modelos que devem segui-lo.
  • Diga a quem viu a página de localhost depois de se cadastrar que a conta está confirmada. Magic links e redefinições de senha precisam de um e-mail novo.
  • Limite os curingas às pré-visualizações do seu time, e nunca permita todos os apps do domínio de um builder.

Se as pessoas apertam o link novo e ouvem que ele expirou, essa mensagem tem uma página própria. O checklist de segurança de 10 minutos cobre o que costuma ficar aberto num app recém-lançado.

Perguntas frequentes

Por que o meu e-mail de confirmação do Supabase abre localhost:3000?

Porque o Supabase mandou a pessoa para a Site URL do seu projeto, e a Site URL ainda é http://localhost:3000. O Supabase usa ela sempre que o endereço que o seu app pediu não está na lista Redirect URLs, ou quando o seu app não pediu nenhum. Troque na página URL Configuration, em Authentication.

A conta está confirmada se o link abriu localhost?

Sim, numa confirmação de cadastro. O Supabase confirma o endereço primeiro e só depois manda o navegador adiante, então a página que falha chega com o trabalho já feito. A pessoa pode entrar no seu site no ar com a senha que escolheu. Um magic link ou uma redefinição de senha precisa ser enviado de novo.

Adicionei o meu site em Redirect URLs e continua indo para localhost. Por quê?

A entrada tem que bater letra por letra com o endereço oferecido ao Supabase: com www ou sem, com https ou sem, e o caminho, porque uma entrada sem /** no final só bate com aquele endereço exato. Se batem e o link ainda ignora, o seu modelo de e-mail monta o link com {{ .SiteURL }}. E corrija a própria Site URL, porque é para ela que todo o resto volta.

Preciso adicionar a minha Site URL na lista Redirect URLs?

Não. O Supabase aceita qualquer endereço no próprio domínio da Site URL sem entrada na lista, em qualquer caminho. A lista serve para os outros endereços: uma versão com www, o endereço publicado no builder, os deploys de pré-visualização.

Posso usar um curinga em Redirect URLs do Supabase?

Para pré-visualizações, limitado ao seu próprio time ou projeto, como nos exemplos do Supabase. Nunca um que cubra endereços que outras pessoas podem criar, como todos os apps em vercel.app ou lovable.app. O Supabase recomenda o endereço exato do seu site em produção.

Escrito por

Vlad Tkachenko

Fundador da Reeve

Passo meus dias olhando apps criados com Lovable, Bolt, v0, Cursor e Replit, e a curta lista de erros que aparecem neles sem parar.

Mais sobre o autor

Leia depois

Todos os artigos

Faça backup do seu banco Supabase antes da próxima mudança

O Reeve guarda uma cópia criptografada em horários programados e a restaura para você se algo quebrar.

Ver backups do Supabase

Verificação externa automatizada, não uma auditoria completa. A ausência de achados não é garantia de segurança.