uk_ct_…. O seu backend precisa
saber quem está chamando, e perguntar para a gente a cada requisição colocaria a
nossa latência e a nossa disponibilidade na frente da sua API.
Então a sessão de contato compra um JWT de vida curta, assinado por ambiente,
que o seu backend verifica offline contra um conjunto de chaves públicas.
Emitindo
Quando a sessão acaba
A sessão opaca vive 30 dias, contados do momento em que foi emitida. Não há renovação deslizante: a data não se move porque alguém continuou usando o produto, e não existe rota para estendê-la. Quando ela vence, a resposta é emitir uma nova. O SDK não descobre isso na próxima chamada — ele avisa no momento em que acontece, mesmo numa aba que só está aberta:subscribe também publica o signedOut, e é o que uma UI deve escutar — ela só
precisa desenhar quem está ali agora. O onSessionExpired é para o código que
precisa fazer algo a respeito, e para esse código as duas formas de uma sessão
acabar são instruções opostas: reautenticar alguém que saiu é um laço, e
reautenticar alguém cujo prazo venceu é a correção inteira. Por isso ele nunca
dispara num signOut().
Renovar antes do fim, não depois
Um host federado deve responder aoonSessionExpiring, que é o mesmo evento cinco
minutos antes — com a sessão ainda de pé:
track() enfileira de forma durável e
descarrega a cada cinco segundos, carimbando cada lote com a sessão que existir
naquele instante — então uma renovação feita depois do vencimento manda tudo o
que caiu no intervalo como anônimo: /v1/events rebaixa em vez de recusar, então
nada falha e nada avisa. O intervalo dura o tempo que o seu código levar para
perceber, e uma aba em segundo plano o alonga.
Repetir a afirmação é legítimo: o hash é um HMAC sobre o external id e não carrega
relógio nenhum, então, enquanto o seu app ainda responder por essa pessoa, ele diz
exatamente o que dizia no carregamento. Um host que já a desconectou responde com
signOut().
É anunciado uma vez por sessão — uma aba restaurada com quatro minutos de vida
ouve na hora, em vez de nunca — e nunca para uma sessão que já venceu, que é o
momento do onSessionExpired e faria você bootar duas vezes pela mesma sessão.
Passe { marginMs } se a sua chamada de identidade precisar de mais espaço.
Cada emissão registra o contato no medidor de contatos ativos do mês, de forma
idempotente. É esse o número que a sua fatura lê — veja
o medidor.
O que o token carrega
string
O id do ambiente. Fixe ele. Um token do ambiente de teste não pode satisfazer
uma verificação de produção.É o
environment.id de GET /v1/me,
a chamada que a sua chave secreta já deveria fazer no start-up — guarde o valor
como USERKIT_ENVIRONMENT_ID. A listagem de ambientes não serve aqui: ela é
do painel, e recusa uma chave de API. O painel mostra o mesmo id em
Configurações → Chaves de API, ao lado da chave publicável.uuid
O id do contato.
uuid
O id da sessão — o que você passa para
DELETE /v1/contact/sessions/{id}.string
O seu próprio id para essa pessoa, presente quando o contato tem um. No modo
federado é nele que o seu backend se apoia.
string
boolean
Se a identidade por trás da sessão foi comprovada. Barre nisso antes de
qualquer coisa por onde os dados de outra pessoa pudessem vazar — um token com
verified: false é uma assinatura perfeitamente válida sobre uma afirmação não
comprovada.uuid
O time dentro do qual esta chamada está agindo — o que o cliente nomeou em
X-Customer-Id na hora de emitir, ou o vínculo mais antigo quando ele não
nomeou nenhum. Ausente para um contato que não pertence a customer nenhum, que
é todo contato B2C e todo contato no primeiro dia.Ausente quer dizer “sem time”, nunca “sem necessidade de checar”. Um id de
customer que o contato não tem é recusado com 404 na emissão, em vez de
produzir um token sem a claim que o seu backend usa como porta.string
A
key do papel que essa pessoa tem nesse time. owner e member estão
em todo ambiente; ao lado deles ficam os que a sua organização definiu, então
isto é uma string aberta e não uma união de dois valores.string[]
O que esse papel concede, e é nisto que o seu backend deve barrar — não no
nome do papel. Dois vocabulários na mesma lista: as que começam com
$ são
nossas e são as únicas que o UserKit avalia ($team.manage,
$billing.manage, $keys.manage, $webhooks.manage); todo o resto é seu,
guardado e devolvido aqui e nunca lido por nós.É esta a claim que faz grades:write ser uma decisão offline: um papel novo
criado no painel chega ao seu gate sem deploy nenhum do seu lado. Veja
Times de customer.boolean
Se quem está segurando esta sessão é alguém do seu suporte em vez da
pessoa que ela nomeia. O contato é real e a identidade foi comprovada — é isso
que “ver o que o cliente vê” quer dizer —, então sirva a requisição; mas um
ato destrutivo feito por aqui está sendo feito pelo suporte, e só o seu
produto sabe quais dos seus atos são esses.Sempre presente, inclusive como
false, ao contrário das claims de time.
A assimetria é de propósito: uma claim ausente teria que ser lida como “não é
impersonação”, que é uma afirmação feita a partir de uma ausência — e é a
direção tranquilizadora, a que faz alguém parar de checar. false é uma
afirmação sobre a qual dá para agir; nada não é.integer
Segundos Unix. A validade é de 5 minutos.
O time viaja no mint, não na sessão
As claims de time seguem o que esta emissão pediu, e é por isso que trocar de time é emitir de novo.@userkit/js faz isso sozinho: setActiveCustomer
descarta o JWT em cache junto com a escolha, então a próxima getToken() mint a
um token que nomeia o time novo.
No servidor não existe time ativo: quem emite é getToken({ customerId }) do
@userkit/nextjs, e o id é o que a sua rota já leu, normalmente da URL. Sem ele
a resposta é o vínculo mais antigo — um time de verdade, e raramente o que a
página está mostrando.
Verificando
Busque o conjunto de chaves uma vez, guarde em cache, e verifique localmente.Guarde o conjunto de chaves em cache
A resposta carrega:POST /v1/contact/token que devolve o 501 jwt_unavailable.
O que as duas diretivas de staleness compram, e o resto do que acontece com o seu
produto num minuto ruim nosso, está em
Disponibilidade e degradação.
Revogação
Um JWT é verificado offline, então nada consegue chamá-lo de volta. Deletar uma sessão interrompe o próximo refresh, não o token que já está numa página — o que, sozinho, faz de cinco minutos o tempo máximo que uma sessão revogada continua funcionando. Cinco minutos é o piso, não a resposta. A resposta é um segundo documento, usado exatamente como o conjunto de chaves: buscado de tempos em tempos, guardado em cache, conferido localmente.revoked com o sid do token. Encerrar uma sessão chega ali na mesma
instrução que a deleta, então toda porta — sair, revogar um dispositivo, “sair de
todos os outros”, o suporte revogando um dispositivo — publica sem que nada
precise lembrar de fazer isso.
integer
O limite que você ganha. Uma sessão revogada para de verificar dentro desses
segundos, em vez de dentro da validade inteira do token.
integer
Até onde o documento alcança para trás. Uma cópia mais velha que isso não diz
nada sobre os tokens vivos agora — descarte em vez de confiar.
Com @userkit/nextjs
Assinatura e revogação, ambas offline, numa chamada só. Nada aqui fala com a
gente no caminho da requisição.
claims.contactId, claims.verified,
claims.impersonated e as do time — claims.customerId, claims.customerRole
e claims.customerPermissions, que é sempre um array (vazio para quem não tem
time), então um gate é um includes e nada antes dele:
claims.revocationCheck diz em qual dos três casos você está — fresh, stale
(a última cópia, ainda dentro da janela dela) ou unavailable (aceito só pela
assinatura e pela expiração). Passe requireRevocationCheck: true para recusar no
unavailable nas poucas ações em que essa é a troca certa; deixe desligado no
resto.
Fazendo na mão
Sessões que o contato enxerga
O token de sessão de contato vive no seu domínio semhttpOnly. Isso faz de “ver
meus dispositivos e encerrar um” parte da defesa desse token, não um extra.
404.
Só sessões. Encerrar um dispositivo não cancela um magic link ou um código por
e-mail que estejam em aberto — isso é uma afirmação sobre a caixa de entrada, não
sobre um notebook, e quem está no meio de um login em outra aba não deveria
perdê-lo. Esses morrem no próprio relógio: uma hora para o link, dez minutos
para o código.
Rotacionando a chave de assinatura
api_keys:write — a mesma trava do segredo de identidade, porque essa chave
emite os tokens em que o seu backend confia.
A chave antiga para de assinar na hora e continua verificando por 24 horas: a
sobreposição que permite aos caches de JWKS se atualizarem e aos tokens em voo
expirarem. Seu backend não precisa de deploy — ele rebusca o conjunto de chaves e
encontra os dois kid.
Numa suspeita de comprometimento a conta corre ao contrário: todo token assinado
com a chave antiga morre em até 5 minutos da rotação. É esse o limite a citar.
O medidor de contatos ativos
customers:read. Um contato conta como ativo num mês quando um JWT é emitido
para ele — então uma sessão aberta em janeiro e renovada em fevereiro está ativa nos
dois.
Só o ambiente live, porque só contatos de produção contam. O mês é UTC, decidido
uma vez no schema: a exibição pode traduzir, uma fatura não pode ser ambígua. Isso
lê a mesma consulta que a fatura lê.