Dois tipos de token no SuperDB
O SuperDB usa dois formatos de token para propósitos distintos. É importante entender qual é qual antes de integrar:
| Token | Formato | Algoritmo | Usado com |
|---|---|---|---|
| Token de end-user (access token) | PASETO v4 — começa com v4.public. |
Ed25519 (chave por projeto) | Auth Service (/auth/v1/*), Admin API (/platform/v1/*) |
| API keys do projeto (anon / service_role) | JWT padrão — xxxxx.yyyyy.zzzzz |
HS256 | PostgREST (api.superdb.com.br), Storage, Realtime |
Token de end-user (PASETO) não funciona com PostgREST. O PostgREST valida JWT HS256 — não PASETO. Para acessar dados via api.superdb.com.br, use as API keys (anon ou service_role) disponíveis no Dashboard em API Keys.
Token de end-user — PASETO v4
Um token PASETO v4 começa com o prefixo v4.public. e é assinado com Ed25519 (chave pública por projeto). O payload carrega:
v4.public.eyJzdWIiOiI3MTM4... ← token completo (começa com v4.public.)
// Payload decodificado:
{
"sub": "7138c8b9-...", // user id (UUID) — usado pelo PostgREST para lookup
"email": "felipe@superdb.com.br",
"role": "member",
"aud": "proj_acme", // projeto ao qual o usuário pertence
"iss": "superdb-auth",
"iat": "2026-05-19T10:00:00Z",
"exp": "2026-05-19T11:00:00Z"
}
API keys — JWT HS256
As API keys (anon e service_role) são JWT padrão, assinadas com HS256. Você as copia do Dashboard e usa no header Authorization: Bearer ao chamar api.superdb.com.br:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 ← header
.
eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGVyZGIifQ ← payload
.
xKn2zX4_3wEW... ← signature
// Payload decodificado (anon key):
{ "role": "anon", "iss": "superdb" }
// Payload decodificado (service_role key):
{ "role": "service_role", "iss": "superdb" }
O payload NÃO é criptografado. Qualquer um que vê o token consegue ler. O que protege é a assinatura: sem o secret, ninguém consegue forjar um token válido. Não bote dados sensíveis no payload (senhas, CPF cru).
Por que importa?
- Stateless. O servidor não precisa guardar sessão num Redis — basta validar a assinatura. Escala horizontalmente sem coordenar.
- Auto-descritivo. O payload já tem o que o backend precisa (user_id, role) — uma query a menos no banco.
- Expira sozinho. Tokens com
exppassado são rejeitados automaticamente. - Ed25519 para end-users. Algoritmo moderno e rápido — a chave pública do projeto pode ser usada por qualquer serviço para verificar sem ter o secret.
Como funciona no SuperDB
O SuperDB Auth emite dois tokens a cada login:
| Token | TTL padrão | Pra que serve | Onde guardar |
|---|---|---|---|
| Access token (PASETO v4) | 1 hora | Autenticar no Auth Service e Admin API | Memória ou cookie httpOnly |
| Refresh token | 30 dias | Trocar por novo access token quando o atual expira | Cookie httpOnly (essencial) |
Ciclo de vida
- User loga → server emite
accessPASETO v4 (1h) +refresh(30d) - SDK guarda os dois (cookie httpOnly por padrão)
- SDK envia
accessem todo request para o Auth Service - Quando o servidor responder
401, o SDK chama/auth/v1/token?grant_type=refresh_token - Recebe um novo
access+ novorefresh(refresh token rotation) - Logout → invalida o refresh no servidor (lista de revogados)
Assinatura
Tokens de end-user usam Ed25519 com chave pública por projeto (rotacionável). API keys usam HS256 com o JWT secret global do PostgREST.
Custom claims
Você pode adicionar campos custom ao payload via o JWT hook:
CREATE OR REPLACE FUNCTION public.custom_jwt(uid uuid, email text)
RETURNS jsonb LANGUAGE sql STABLE AS $$
SELECT jsonb_build_object(
'plan', p.plan_name,
'team_id', tm.team_id,
'is_admin', u.is_admin
)
FROM auth.users u
LEFT JOIN public.plans p ON p.user_id = u.id
LEFT JOIN public.team_members tm ON tm.user_id = u.id
WHERE u.id = uid;
$$;
-- Registrar como hook
INSERT INTO auth.hooks (hook_name, hook_table_name)
VALUES ('custom_access_token', 'public.custom_jwt');
Aí no SQL você lê com auth.jwt() ->> 'plan':
CREATE POLICY "pro features"
ON premium_data FOR SELECT
USING (auth.jwt() ->> 'plan' = 'pro');
Quando usar (e quando não)
Use JWT pra…
- Identificar usuário no backend — todo handler do SuperDB já faz isso
- Passar claims pra RLS —
auth.uid(),plan,team_id - Permissões cross-service — mesmo token vale no REST e no Storage
NÃO use JWT pra…
- Guardar dados sensíveis no payload. Não é encriptado — só assinado
- Sessões longas sem rotação. Token vazado → atacante tem acesso pelo TTL inteiro. Mantenha access em 1h, não 24h
- Revogação em tempo real. JWT é "stateless" — pra invalidar antes da expiração, você precisa de blocklist (custo de stateful)
Exemplos práticos
1. Decodar payload no client
function decodeJwt(token) {
const [, payload] = token.split('.')
return JSON.parse(atob(payload))
}
const { data: { session } } = await db.auth.getSession()
const claims = decodeJwt(session.access_token)
console.log(claims.email, claims.role, claims.exp)
// felipe@superdb.com.br authenticated 1715000000
2. Ler claim no SQL
-- Dentro de uma policy ou function
SELECT auth.jwt() ->> 'plan'; -- 'pro'
SELECT auth.jwt() -> 'team_ids'; -- ["t1","t2"]
SELECT auth.uid(); -- '7138c8b9-...'
3. Forçar refresh manual
// Se você suspeita que o token está stale (ex: user mudou plan)
const { data, error } = await db.auth.refreshSession()
// data.session tem o novo JWT com os claims atualizados
Armadilhas comuns
- Confiar em claim sem verificar assinatura. Nunca leia o payload diretamente no backend pra autorizar. Use o SDK (que valida a assinatura) ou o middleware oficial.
- Guardar em
localStorage. Aberto pra XSS — JS de outro origem pode ler. Prefira cookiehttpOnly; Secure; SameSite=Lax. - TTL muito longo. Access de 24h significa 24h de acesso pro atacante caso vaze. 1h é o sweet spot — refresh cobre UX.
- Não rotacionar refresh. Refresh token rotation (já default no SuperDB) detecta replay attacks — não desligue.
- Custom claim com nome reservado. Não use
sub,iss,aud,exp,iat,nbf— são da RFC 7519 e podem causar bugs. - Payload gigante. Cada request leva o JWT no header — 8KB de claims = 8KB extra por request. Evite arrays grandes; passe ID, faça lookup no banco.
Debug: cole o token em jwt.io pra ver o payload formatado. Ele não verifica a assinatura sem a chave — útil só pra inspeção.
Termos relacionados
- Service-role vs anon — ambos são JWTs, mas com claims diferentes
- RLS — usa os claims do JWT (
auth.uid(),auth.role()) - Cookbook: JWT hook — adicionar custom claims
- Glossário: JWT
- RFC 7519 — JSON Web Token