Skip to main content
Três peças.
  • A API, em https://api.userkit.dev — dona dos dados e de todas as regras.
  • O painel, em app.userkit.dev — onde o seu time administra a organização.
  • Seu produto — o que você estiver construindo. Fala com a API usando uma chave de API da organização a partir do seu servidor, e uma chave publicável a partir das suas páginas.

O painel nunca entrega um token ao navegador

As telas do painel nunca carregam uma credencial: o token de sessão fica num cookie httpOnly e é o servidor do painel que anexa o header Authorization. Nenhum XSS consegue ler aquele token, e os endpoints que respondem com uma credencial — login, dois fatores, cadastro — gravam o valor direto no cookie em vez de entregá-lo ao script. Vale como padrão para o seu próprio front: o que o navegador não segura, um ataque no navegador não rouba.

Seu produto, esse sim, fala direto com a API

O plano de clientes tem a postura oposta, de propósito.
/v1/boot, /v1/contact-auth/* e /v1/contact/* respondem a qualquer origem, porque são chamados do seu domínio por definição. CORS não é o portão ali: a lista de origens permitidas da chave publicável é, e nenhum cookie viaja nessas requisições. Todo o resto continua na allowlist estreita.

Autorização é uma consulta

Um usuário pertence a uma organização por um vínculo. O vínculo carrega um papel. Um papel é um conjunto de permissões. O catálogo de permissões vive em Go — o conjunto de coisas que o sistema sabe fazer é um fato do código. Quem tem o quê vive no banco, então uma organização pode definir os próprios papéis sem esperar um deploy. Resolver uma sessão resolve tudo isso numa ida só: identidade, organização ativa, papel, permissões. O JOIN do vínculo é a autorização, o que tem duas consequências que vale internalizar:
  • revogar um vínculo invalida aquela sessão imediatamente, sem cache para esperar;
  • um código de organização que alguém cole na URL não casa com linha nenhuma, a menos que a pessoa seja membro.
A organização viaja na URL (/{org_code}/…) e no header X-Organization-Id. Ela identifica; não autoriza. Trocar de organização é navegação, não mutação.
No painel, can(session, PERMISSIONS.x) esconde o que um papel não pode fazer. Isso é gentileza. requirePermission na API é a aplicação da regra.

Ambientes atravessam tudo

Toda organização tem exatamente dois: live e test, criados junto com ela e não criáveis nem deletáveis. As tabelas do plano de clientes carregam environment_id ao lado de organization_id. Uma credencial de máquina resolve o ambiente a partir da linha da chave armazenada, em toda requisição. Não existe parâmetro de ambiente nessa superfície, nunca — o que faz de “apontei o staging para produção” algo que não pode acontecer. O painel é a exceção, e deliberadamente: staff é autorizado na organização inteira, então as leituras do painel recebem ?environment=live|test como um parâmetro explícito de visão.

Capacidades vêm na resposta da sessão

GET /v1/session carrega two_factor_available e uploads_available. Elas descrevem o servidor, não a sua organização: um cliente as lê para esconder o que a plataforma não pode fazer naquele momento, em vez de descobrir por um 501 no meio de um fluxo. GET /health responde o estado das dependências. Só o banco muda o código de status.

Um barramento interno de eventos

Mutações de domínio publicam num outbox na mesma transação das linhas que descrevem, então um evento nunca descreve uma escrita que sofreu rollback. Um commit cutuca a fila para drenar; uma varredura periódica é o que garante a drenagem quando toda cutucada se perde. Veja Eventos de domínio.