Noções de segurança
Supabase invalid JWT: qual chave parou de funcionar?
Um erro Supabase invalid JWT é o PGRST301: um token assinado não passou na verificação. Num projeto novo isso é a sessão, não a chave de API.

Em resumo
- Um erro Supabase invalid JWT chega como PGRST301 com HTTP 401, e significa que a assinatura do token que seguiu na sua requisição não pôde ser verificada. Nada foi procurado numa lista e recusado.
- As chaves publishable e secret são cadeias curtas sem assinatura, então num projeto que já migrou para elas este erro fala do seu usuário logado e nunca da chave de API.
- O PostgREST 13 trocou uma frase vaga por uma lista curta de frases concretas. Leia a mensagem que você recebeu de fato: cada uma descarta uma causa diferente.
Ontem seu aplicativo funcionava. Hoje de manhã toda requisição volta com 401, nada mudou no seu código, e a resposta fala de um JWT, uma coisa que você nunca digitou em lugar nenhum.
{
"code": "PGRST301",
"message": "JWT cryptographic operation failed"
}
Um erro Supabase invalid JWT é aquele que aparece depois que você fez a coisa certa. Você trocou uma chave porque um guia mandou, ou migrou seu projeto para as chaves novas, e o aplicativo ficou mudo.
E é aqui que guia após guia erra: a chave de que esta mensagem fala muitas
vezes não é a que você acabou de trocar. Pesquise o erro e vão mandar você
conferir sua chave anon. Num projeto que migrou para as chaves publishable e
secret, a chave de API não é um token assinado de jeito nenhum, então o que
falhou na verificação era outra coisa na requisição.
O que significa um erro Supabase invalid JWT?
Alguma coisa na sua requisição era um token assinado, e a assinatura não bateu.
Uma senha é uma palavra que alguém procura numa lista. Um JWT não é isso. É uma carta fechada com lacre: a pessoa na porta não procura você em lugar nenhum, ela segura um sinete e confere se o lacre da sua carta foi feito com ele. Se foi, você passa, e ninguém lê lista nenhuma.
Uma carta lacrada com o sinete errado é recusada mesmo com cada palavra certa lá dentro. E no momento em que a porta troca de sinete, nenhuma carta que já está a caminho abre mais, sem que ninguém tenha encostado nas cartas.
Sua requisição é a carta. Seu projeto do Supabase segura o sinete. Um 401 aqui quer dizer que os dois não combinaram.
A mensagem mudou no PostgREST 13 e o código não
Teste por PGRST301. A frase ao lado depende da versão do PostgREST que seu
projeto roda, e as mais novas dão uma lista curta delas.
A referência de erros do PostgREST descreve PGRST301 como um JWT recebido que
não pôde ser decodificado ou que é inválido, em HTTP 401, tanto na versão 13
quanto na 14. Essa frase é a descrição da tabela de documentação. Não é o que
aterrissa no seu campo message. Lendo o Error.hs no branch main do PostgREST,
esse campo traz uma destas:
| O que a mensagem diz | O que isso diz a você |
|---|---|
Empty JWT is sent in Authorization header | O cabeçalho foi enviado e veio vazio. |
Expected 3 parts in JWT; got 1 | O que chegou não é um JWT. Um JWT são três blocos separados por pontos. |
No suitable key or wrong key type | O servidor não tem nenhuma chave capaz de verificar um token desse tipo. |
Wrong or unsupported encoding algorithm | O token nomeia um algoritmo de assinatura que o servidor não usa. |
JWT cryptographic operation failed | O token está bem formado e a assinatura não bate. |
Unsupported token type | O token tem um formato que o PostgREST não lê. |
JWT cryptographic operation failed é o que quer dizer chave velha. O token foi
montado certo e assinado por algo que o projeto não guarda mais, que é
exatamente o que uma rotação deixa para trás.
Projetos antigos veem coisa bem diferente, e daí vem a cadeia que aparece em todo
canto nos resultados de busca. Antes da versão 13, o PostgREST imprimia o que a
biblioteca de JWT tinha dito, exatamente como ela disse. É daí que sai
JWSError JWSInvalidSignature: é um valor de biblioteca impresso na tela, não
uma frase que alguém escreveu para você. O changelog do PostgREST para a 13.0.0
descreve a mudança como mensagens de erro diagnósticas no lugar de internos
expostos.
Vale conhecer um código vizinho enquanto você está por aqui. PGRST303, também
401, é o que você recebe quando a assinatura foi verificada e o conteúdo do token
foi recusado: expirado, ainda não válido, ou com a audiência errada. Se for esse
o seu código, pare de olhar as chaves e vá olhar o relógio e a sessão.
Qual chave é: a de API ou o token do seu usuário?
Depende de se o seu projeto ainda usa anon e service_role.
A documentação do Supabase sobre chaves de API é explícita quanto à diferença. As
chaves antigas anon e service_role são JWT de longa duração, e é por isso que
começam com eyJ. As chaves publishable e secret mais novas são cadeias curtas e
não são JWT de jeito nenhum.
Esse único fato parte o erro em dois:
- Seu projeto ainda usa
anoneservice_role. A chave de API da sua requisição é ela mesma um token assinado, então uma chave de outro projeto, uma chave de antes da rotação ou uma colagem cortada falham na verificação de assinatura exatamente como falharia uma sessão ruim. Qualquer um dos dois tokens da requisição pode ser o que falhou. - Seu projeto migrou para
sb_publishable_esb_secret_. A chave de API deixou de ser um token, então não há assinatura nela para nada rejeitar. O que resta verificar é o token de acesso do seu usuário logado, que o Supabase Auth emite separado da chave de API.
Na prática, a primeira pergunta não é qual chave está errada. É se havia alguém logado quando a requisição falhou. Se uma página sem login funciona e uma com login não, você está olhando sessões. Se tudo falha, inclusive uma página que não pede nada pessoal, você está olhando a chave de API, e seu projeto é dos antigos.
Se forem sessões e seu log do Auth também disser Invalid Refresh Token, seus
usuários estão sendo desconectados porque
a sessão por trás do token deles terminou.
Se a resposta veio do Supabase Auth e diz invalid claim: missing sub claim, o
token passou na verificação da assinatura e não nomeava ninguém. É uma chave
enviada onde deveria ir o token de uma pessoa, e
essa mensagem tem um post próprio.
Invalid JWT não é "No API key found in request"
São duas mensagens diferentes com duas causas diferentes, e as correções não têm nada em comum.
Um invalid JWT quer dizer que um token chegou e a assinatura não bateu. "No API
key found in request" quer dizer que nenhum
cabeçalho apikey chegou, então a requisição foi recusada no gateway antes de
qualquer coisa ser perguntada ao seu banco.
A própria resposta resolve qual dos dois é. Uma chave faltando é recusada pelo
gateway na frente do seu projeto, então o corpo traz um message e um hint e
nenhum code. Um invalid JWT foi longe o bastante para ser lido pelo PostgREST,
e por isso volta com PGRST301 escrito nele.
Onde a chave antiga ainda está, por ordem
Quatro lugares, e vale conferir nesta ordem porque é mais ou menos a frequência com que cada um é a resposta.
- As configurações de projeto da sua ferramenta. Lovable, Bolt, v0 e Replit guardam cada um o próprio conjunto de variáveis de ambiente, e se você construiu com Cursor ou Claude Code o mesmo conjunto fica onde você faz o deploy. É a cópia que quase todo mundo atualiza, e a única que quase todo mundo atualiza.
- Uma chave escrita direto no código. Ela viaja dentro do JavaScript que seus visitantes baixam, então mudá-la no painel não muda nada até o arquivo ser editado e o site reconstruído. Onde fica essa página e o que cada valor nela faz, se você ainda não abriu.
- Secrets de Edge Function. Uma função guarda o próprio conjunto, separado do do seu frontend, e continua usando a chave antiga depois que todo o resto já mudou. O Supabase também avisa que uma função com a opção Verify JWT ligada pode quebrar ao rotacionar uma chave de assinatura.
- Um deploy que ninguém reconstruiu. A chave ficou embutida na última construção, então a cópia no ar continua mandando ela até algo disparar uma nova. Seu painel mostra a chave nova esse tempo todo, e é isso que torna este caso difícil de ver: o lugar onde você vai conferir é o único que já está certo.
Qual chave seu aplicativo em produção manda de verdade
Abra o site publicado em vez da prévia e leia o que a página manda.
Uma chave antiga do Supabase é uma cadeia longa começando com eyJ. As mais
novas se anunciam nos primeiros caracteres: sb_publishable_ pertence ao seu
aplicativo e sb_secret_ nunca. Se você encontrar a segunda numa página que um
visitante consegue carregar, pare este artigo e
troque ela antes de qualquer outra
coisa. Qual das duas você tem, e
o que mudou quando o Supabase renomeou as duas,
são coisas a resolver antes de começar a substituir valores.
Para ver o que seu projeto emite agora: todas as chaves ficam num lugar só no painel, em Settings e depois API Keys, as antigas inclusive.
Se você prefere não ler um bundle minificado, nosso escaneamento gratuito lê seu aplicativo em produção como um estranho leria e aponta as chaves visíveis de fora. Leva uns 20 segundos e não precisa de conta: escanear seu aplicativo.
Rotacionar uma chave de assinatura sem deslogar todo mundo
Qual dos dois caminhos de rotação você segue decide se seus usuários percebem.
A documentação do Supabase sobre chaves de assinatura põe os dois lado a lado.
Rotacionar o secret JWT antigo desloga na hora todos os usuários ativos, e
anon e service_role precisam ser rotacionadas ao mesmo tempo. As JWT
signing keys mais novas funcionam de outro jeito: uma chave nova começa como
standby, a rotação a torna a atual, e a que ela substitui passa a ser a anterior.
Enquanto for assim, assinaturas das duas chaves continuam sendo aceitas, e as
cartas lacradas semana passada ainda abrem. Revogar a chave antiga é o passo
separado que encerra isso.
Dois avisos nessa mesma documentação são os que pegam as pessoas:
- Um componente que verifica tokens por conta própria contra o secret JWT antigo, com uma biblioteca como jose ou jsonwebtoken, quebra na rotação. Ele segura o sinete velho e ninguém disse para ele trocar.
- Uma Edge Function com Verify JWT ligada pode quebrar pelo mesmo motivo.
Criar chaves publishable e secret também não revoga suas chaves antigas. Elas continuam funcionando até você desativá-las, o que a documentação marca como um passo à parte. Então um aplicativo que migrou só metade das cópias segue funcionando por semanas, e o dia em que alguém desativa o par antigo é o dia em que a outra metade começa a responder 401.
Ficar de olho num aplicativo que começou a responder 401
Um aplicativo que devolve 401 em toda requisição continua no ar, continua servindo uma página e continua respondendo a qualquer checagem de saúde. Nada fica vermelho no seu painel. Você descobre quando alguém te escreve.
O Reeve Monitor descobre primeiro:
- as nove verificações a cada hora, em até três aplicativos
- um e-mail no dia em que um novo escaneamento dá uma nota pior que a anterior
- se o aplicativo responde, a cada 60 segundos
- um relatório mensal do que ele viu
Uma chave que você teve de trocar com pressa costuma ser uma chave que estava onde não devia, e daqui você não enxerga o que foi lido ou alterado com ela enquanto ela esteve fora. O Reeve Care guarda uma cópia do seu banco de dados Supabase de antes disso.
- uma cópia criptografada conforme um calendário, guardada no armazenamento do próprio Reeve na UE, fora da sua conta Supabase
- cada cópia verificada antes de contar, contando as linhas de cada tabela
- uma restauração em um clique que salva o estado atual antes de substituí-lo
- seus arquivos enviados também, assim que você conectar um acesso de Storage
- tudo o que o Monitor faz
O que fazer hoje
O que fazer
- Leia o campo
code, não a frase.PGRST301é uma assinatura que não bateu,PGRST303é um token cujo conteúdo foi recusado, como um expirado. - Descubra qual token falhou antes de trocar qualquer chave. Se as páginas sem login funcionam e as com login não, é uma sessão e não sua chave de API.
- Confira os quatro lugares onde a chave antiga se esconde: as configurações da ferramenta, o próprio código, os secrets de Edge Function e um deploy que ninguém reconstruiu.
- Faça a rotação de uma sentada. Cada cópia deixada para trás é um aplicativo funcionando pela metade, e o site no ar continua mandando a chave antiga até ser reconstruído.
- Nas chaves de assinatura novas, revogar é o passo que de fato encerra as assinaturas antigas. Rotacionar sozinho deixa elas aceitas.
Comece perguntando se uma página sem login ainda carrega, porque essa única resposta decide qual metade deste artigo é a sua. Se aparecer uma chave secreta exposta, trocá-la cobre a ordem em que agir, e quais chaves algum dia foram seguras de publicar conta por que as duas ficaram tão parecidas.
Perguntas frequentes
O que significa PGRST301?
PGRST301 é o PostgREST respondendo HTTP 401 porque não conseguiu verificar um token assinado que seguia na sua requisição. O PostgREST é o servidor que transforma suas tabelas do Supabase na API HTTP com que seu aplicativo conversa. A referência de erros dele descreve o código como um JWT recebido que não pôde ser decodificado ou que é inválido, e a partir da versão 13 o campo message traz uma frase mais específica sobre qual parte da verificação falhou.
Por que recebo 401 depois de trocar minha chave do Supabase?
Porque alguma cópia da chave antiga continua sendo enviada. Os lugares de sempre são as configurações de projeto da sua ferramenta, uma chave escrita direto no código, um secret de Edge Function e um deploy que foi construído antes da troca e nunca mais reconstruído. Um token assinado por uma chave que o servidor não aceita mais não passa na verificação, e isso chega até você como 401 em toda requisição em vez de como um aviso sobre a chave.
Qual a diferença para No API key found in request?
São duas mensagens diferentes com causas diferentes. No API key found in request significa que a requisição chegou sem nenhum cabeçalho apikey e foi recusada no gateway antes de o banco ser consultado. Um invalid JWT significa que o token estava lá e a assinatura não bateu. O primeiro é uma carta sem endereço, o segundo é uma carta com o lacre errado.
Preciso atualizar minhas Edge Functions também?
Sim, e é a cópia que mais se esquece, porque uma Edge Function guarda os próprios secrets, separados dos que seu frontend usa. O Supabase também avisa que uma função com a opção Verify JWT ligada pode quebrar ao rotacionar uma chave de assinatura, e que qualquer componente que verifique tokens por conta própria contra o secret JWT antigo vai quebrar pelo mesmo motivo.
Como vejo qual chave meu aplicativo em produção realmente envia?
Abra o site publicado, não a prévia, e veja o que a página envia de fato. Uma chave antiga do Supabase é uma cadeia longa começando com eyJ, e as mais novas começam com sb_publishable_ ou sb_secret_. Nosso escaneamento gratuito lê um aplicativo em produção como um estranho leria e aponta as chaves visíveis de fora, o que também mostra se uma antiga ainda está sendo enviada.