- 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
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.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 carregamenvironment_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.