Skip to main content
O Supabase Auth é a sua fonte da verdade. O UserKit precisa saber qual dos seus usuários está na página, e não vai aceitar a palavra da página — o seu servidor assina a afirmação.
O identity secret nunca entra no bundle. Nunca prefixe com NEXT_PUBLIC_, nunca renderize no HTML, nunca mande para o navegador para economizar uma ida e volta. Quem tem esse segredo emite sessão verificada para qualquer um dos seus usuários.
O contrato é o da página de identidade federada, e nada aqui o altera:

O que o Supabase fornece

Um valor: user.id, o uuid em auth.users. É esse o external_id.
lib/supabase.ts
getUser(), não getSession(). getSession() decodifica o cookie que o navegador mandou — assinar o conteúdo dele seria assinar um valor que o navegador escolheu. getUser() pergunta ao Supabase.

A metade do servidor

app/api/userkit-boot/route.ts
Deslogado responde 401, e não um par vazio: “nenhum usuário” e “um usuário cujo hash eu não consegui calcular” são situações diferentes, e o próximo passo de quem chamou também é.

A metade do cliente

getState().verified só é true quando o HMAC conferiu. Logado no Supabase com verified: false significa que o segredo é do outro ambiente ou que a mensagem assinada não era exatamente o external_id.

Configuração

O que não viaja

Um usuário do Supabase carrega email e email_confirmed_at. Nenhum dos dois vira identidade no UserKit: um e-mail enviado pelo /v1/boot é guardado como atributo, não resolve para um contato existente e não vira aresta de identidade. O HMAC prova o external_id e só ele. Para provar um endereço, mande um magic link ou um código por e-mail — os dois fluxos que chegam nele.

O exemplo completo

Um app Next executável com esses arquivos, um .env.example e as partes que esta página deixa de fora.