Pular para o conteúdo
Nelcota
EN : Read in English

JWT e papéis

3 min de leitura Editar no GitHub

Contrato público. O formato do JWT é nosso e está documentado aqui; nenhuma biblioteca o determina.

Pré-requisitos

Você precisa de um banco PostgreSQL privado inicializado e uma chave de assinatura ou verificador compatível. Esta página descreve tokens/papéis nativos; o verificador nativo não exige uma política de emissor e audiência.

Papéis do Postgres

Papel Login Usado por RLS
authenticator sim a conexão da API (pool) não se aplica
anon não requisições sem Authorization aplicada
authenticated não usuários finais autenticados aplicada
service_role não backend confiável ignorada (BYPASSRLS)
nelcota_auth não interno: o servidor lendo auth.* não se aplica (nenhum JWT pode assumir esse papel)

authenticator é NOINHERIT: por si só, não tem nenhum privilégio. Ele é membro dos outros papéis e assume um deles a cada requisição.

Aviso: Um JWT com role: service_role ignora toda a RLS. Ele NUNCA deve ir para o frontend, um aplicativo móvel ou qualquer lugar fora do seu servidor.

O que acontece em cada requisição

  1. A API lê Authorization: Bearer <jwt> (o esquema não diferencia maiúsculas de minúsculas: bearer <jwt> também funciona).
    • Sem o cabeçalho: a requisição assume anon.
    • Cabeçalho presente, mas inválido (assinatura, exp, esquema, kid desconhecido ou papel): 401. Nunca assume anon silenciosamente.
  2. Abre uma transação e executa, com parâmetros:
    SQL
    SELECT set_config('role', $1, true),                -- = SET LOCAL ROLE
           set_config('request.jwt.claims', $2, true);  -- full claims
  3. Executa a consulta. A RLS do Postgres decide o que o papel pode ver.
  4. COMMIT (ou ROLLBACK em caso de erro). O papel e as claims terminam com a transação.

O acesso às linhas do aplicativo é determinado por GRANTs e RLS do PostgreSQL. O servidor também valida autenticação, sintaxe de consulta/caminho e fronteiras da operação antes de executar a requisição.

Assinatura

  • EdDSA (Ed25519), o padrão. Chave privada em NELCOTA_JWT_PRIVATE_KEY (PKCS#8 em PEM ou base64 em uma única linha). A chave pública é publicada em GET /auth/v1/.well-known/jwks.json (kty: OKP, crv: Ed25519, kid = impressão digital RFC 7638). O cabeçalho de cada token contém o kid.
  • HS256, o modo simples/legado, com NELCOTA_JWT_SECRET (≥ 32 caracteres). Quando há uma chave EdDSA, o segredo HS256 serve apenas para verificar tokens antigos, permitindo migrar sem encerrar a sessão de ninguém.

Gere uma chave: openssl genpkey -algorithm ed25519 (ou nelcota init, que gera uma).

Claims dos tokens de usuário (emitidos por /auth/v1)

JSON
{
  "iss": "nelcota",
  "aud": "authenticated",
  "sub": "3f1c…-uuid",
  "role": "authenticated",
  "email": "[email protected]",
  "session_id": "8a2d…-uuid",
  "iat": 1767225600,
  "exp": 1767226500
}

Regras de validação (qualquer emissor)

Claim Obrigatória Regra
role sim anon, authenticated ou service_role. Qualquer outro valor → 401.
sub apenas para authenticated o UUID do usuário (auth.uid()).
exp sim época em segundos; tolerância de 30 s.
iss, aud não informativa: não é exigida na verificação.

As outras claims são repassadas sem alterações para auth.jwt(). O logout revoga a sessão e os tokens de renovação; o JWT de acesso, que não mantém estado, continua válido até exp (padrão: 15 minutos, NELCOTA_JWT_EXPIRY_SECS).

Trocar de provedor de autenticação

Como as políticas dependem apenas de auth.uid(), auth.role() e auth.jwt(), qualquer emissor que produza um JWT com as claims acima funciona. A verificação fica por trás do trait JwtVerifier; aceitar um JWKS externo (Auth0, Keycloak, Zitadel...) requer uma nova implementação desse trait.

Funções SQL

SQL
auth.jwt()  -> jsonb  -- request claims ('{}' outside a request)
auth.uid()  -> uuid   -- the sub claim
auth.role() -> text   -- the role claim

Exemplo de política:

SQL
CREATE POLICY owner ON public.todos
    FOR SELECT TO authenticated
    USING (user_id = auth.uid());

Códigos de status HTTP

Situação Status
JWT inválido ou expirado, papel desconhecido ou esquema diferente de Bearer 401 invalid_token
anon sem permissão na tabela 401 db_error (sqlstate 42501)
authenticated sem permissão / violação de política na escrita 403 db_error (sqlstate 42501)
A tabela não existe 404 not_found

O corpo é o JSON de erro da API (code, message e, para erros do banco, sqlstate/details/hint/constraint); veja API REST.

Toda resposta 401 inclui WWW-Authenticate: Bearer error="invalid_token" quando um token foi apresentado e rejeitado, ou apenas Bearer quando a requisição não tinha token (uma requisição anon que precisa se autenticar).

Veja schema de autenticação, API REST e deploy.