name: supabase-edge-functions-jwt-401-debug
description: Debug JWT 401 errors in Supabase Edge Functions on self-hosted VPS — Kong gateway blocks before edge-runtime
tags:
- supabase
- edge-functions
- jwt
- kong
- self-hosted
- debug
last_updated: 2026-06-02
Supabase Edge Functions — Debug JWT 401 on Self-Hosted VPS
Symptom
Supabase Edge Function (/_serverFn ou /functions/v1/manage-users) retorna 401 Unauthorized para todos os usuários, mesmo tokens válidos obtidos via GoTrue login.
Root Cause Pattern (VERY COMMON)
Edge Functions usam SERVICE_ROLE_KEY (chave RSA) para verificar JWTs de usuários, mas tokens de usuário são assinados por GoTrue com JWT_SECRET (chave HS256). São algoritmos e chaves completamente diferentes — verificação sempre falha.
Regra fixa:
- - Tokens de usuário (login via
/auth/v1/token?grant_type=password) → assinado por GoTrue comJWT_SECRET+ HS256 - - Tokens de service role → assinatura RS256 com
SERVICE_ROLE_KEY - - Sempre alinhar
JWT_SECRETentreauth-1(GoTrue) efunctions-1(Edge Runtime) - - Nunca usar SERVICE_ROLE_KEY para verificar tokens de usuário
- - Testar JWT localmente com Python (usando a mesma chave do .env) antes de culpar o código
- - Adicionar console.error/log no início do handler para confirmar que a função é alcançada
- - Supabase self-hosted (Docker) no VPS 31.97.243.106
- - Kong 2.x como API gateway
- - GoTrue para auth (mesmo projeto)
- - Rede Docker: Kong → functions-proxy (nginx:9000) → functions-1 (edge-runtime:9000)
- - Kong stripped
/functions/v1/prefixo antes de enviar ao edge-runtime
How to Verify
`bash
1. Obter token do usuário
TOKEN=$(curl -s -X POST https://seudominio.com/auth/v1/token?grant_type=password \
-H "Content-Type: application/json" \
-d '{"email":"user@test.com","password":"senha"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['access_token'])")
2. Verificar localmente com JWT_SECRET (HS256) — ISSO FUNCIONA
python3 -c "
import jwt, base64
token = 'TOKEN_AQUI'
secret = 'JWT_SECRET_DO_SEU_ENV'
try:
decoded = jwt.decode(token, secret, algorithms=['HS256'])
print('✅ JWT válido!', decoded)
except Exception as e:
print('❌ JWT inválido:', e)
"
3. Se o passo 2 funciona mas a função retorna 401 → o problema é NA FUNÇÃO
A função está verificando com a chave errada
`
Fix — Edge Function Code
No arquivo functions/manage-users/index.ts:
`typescript
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
// JWT_SECRET do .env — mesma que o GoTrue usa para assinar
const JWT_SECRET = Deno.env.get("JWT_SECRET")!;
// NÃO use SERVICE_ROLE_KEY para verificar tokens de usuário
// SERVICE_ROLE_KEY é RSA, tokens de usuário são HS256
// Função para verificar JWT manualmente
function verifyJWT(token: string): any {
const [headerB64, payloadB64, sigB64] = token.split(".");
const sig = base64urlDecode(sigB64);
const data = new TextEncoder().encode(${headerB64}.${payloadB64});
// HMAC-SHA256 com JWT_SECRET
// Usa a crypto API do Deno
}
`
Principle: Nunca use SERVICE_ROLE_KEY para verificar tokens de usuário no edge function. Use sempre JWT_SECRET com algoritmo HS256.
Kong 401 Before Edge Function
Se Kong retorna 401 ANTES de alcançar o edge-runtime (teste: wget http://functions-proxy:9000/any-path retorna 401), o Kong está fazendo validação JWT inline. Kong 2.x pode ter plugins de JWT carregados por default em algumas configurações.
Debug:
`bash
Ver rotas Kong
docker exec deploy-vps-kong-1 kong routes ls 2>/dev/null || true
Ver logs do Kong ao fazer requisição
docker logs deploy-vps-kong-1 --since 30s -f
Observe se Kong loga "Unauthorized" antes de dar 401
`
Possível solução: Kong pode estar usando auth-jwt plugin não explícito. Pode precisar restart para recarregar declarative config.
Functions Container Crash Loop
Se o container deploy-vps-functions-1 só loga shutdown signal received: 15 e nunca printa "listening on 0.0.0.0:9000":
1. Edge-runtime pode estar sendo killed por OOM (memória insuficiente)
2. O binary edge-runtime (não é o deno padrão) pode ter dependências faltando
3. Healthcheck do docker-compose pode estar a matar o container
Debug:
`bash
Ver uso de memória do container
docker stats deploy-vps-functions-1 --no-stream
Ver restart count
docker inspect deploy-vps-functions-1 | grep -i restart
`
Prevention
Verification Steps (After Fix)
1. Login via GoTrue → obter access_token
2. Verificar localmente com Python + JWT_SECRET → deve retornar decoded payload
3. Fazer requisição real → se 401 ainda, o problema é upstream (Kong ou nginx), não a função