Identidade
Saiba quem está jogando, por um token de cinco minutos assinado pelo uloggd e verificado pelo seu servidor, offline.
O cliente de um jogo pode afirmar qualquer coisa sobre quem está jogando. O seu servidor precisa de uma prova, e a recebe como um token de identidade: um JWT assinado pelo uloggd para um jogador, um aplicativo e um nonce que o seu servidor emitiu, válido por cinco minutos. O seu servidor o verifica offline, com as chaves públicas do uloggd. Não há API do uloggd para chamar nem segredo para guardar.
O fluxo
- O seu servidor emite um nonce, amarra-o à sessão que vai receber o token e o guarda.
- O jogo chama
authorize({ scopes: ["identify"] })uma vez, e depoisgetIdentityToken({ nonce }). - O jogo manda o token ao seu servidor.
- O seu servidor o verifica, gasta o nonce e abre a própria sessão para o
sub.
Verifique de novo a cada início de sessão, e mantenha as suas sessões em no
máximo 24 horas. Um jogador que revoga o acesso do seu jogo para de receber
tokens na hora, mas as sessões que o seu servidor já abriu são suas para
encerrar: o evento de dados
grant_revoked avisa quando.
O token
header { "alg": "ES256", "typ": "uloggd-arcade-id+jwt", "kid": "arc-2026-10" }
payload {
"iss": "https://uloggd.com",
"aud": "ID_DO_SEU_APLICATIVO",
"sub": "4f1c2b7e-...", o id do jogador no seu jogo
"nickname": "Ada",
"scope": "identify",
"iid": "0b7e9a31-...", a partida, como instance_id na URL de abertura
"mode": "live", ou "test"
"nonce": "...", o que o seu servidor emitiu
"iat": 1791860000,
"exp": 1791860300, iat + 300
"jti": "9d2e5f60-..."
}subé um uuid exclusivo deste jogador neste jogo; outro jogo vê um diferente para a mesma pessoa. Ele é o mesmo de uma partida para outra, e só muda quando o jogador escolhe Esquecer este jogo.nicknameé o apelido que o jogador escolheu para o seu jogo. Ele pode mudar, e o evento de dadosnickname_changedavisa.modeétestquando você joga um lançamento seu no modo de teste, ou a sua URL de desenvolvimento. Um token assim tem o mesmoisse o mesmoaudde qualquer outro, e nenhuma informação própria. Quem revisa joga emlive, com umsube um apelido novos a cada revisão.- Nunca vêm nele: o e-mail, os ids internos do uloggd, a data de nascimento, a idade, o endereço IP.
Verificar em Node
import {
verifyIdentityToken,
ArcadeTokenError,
} from "@uloggd/arcade-sdk/server";
app.post("/session", express.json(), async (req, res) => {
const nonce = takeNonce(req); // o nonce emitido para esta sessão; vale uma vez
try {
const player = await verifyIdentityToken(req.body.token, {
appId: process.env.ULOGGD_APP_ID,
nonce,
});
// { subject, nickname, scopes, instanceId, mode, id, issuedAt, expiresAt }
if (!(await rememberOnce(player.id))) return res.status(409).end();
res.json({ session: await openSession(player.subject, player.nickname) });
} catch (error) {
if (error instanceof ArcadeTokenError)
return res.status(401).json({ error: error.code });
throw error;
}
});Num servidor CommonJS, require("@uloggd/arcade-sdk/server") dá as mesmas
funções.
| Opção | |
|---|---|
appId | O id do seu aplicativo. Obrigatório. |
nonce | O nonce que o seu servidor emitiu. Obrigatório. |
allowTestMode | false recusa os tokens do modo de teste (mode: "test"). Por padrão eles são aceitos. |
allowMock | Aceita os tokens do simulador (iss: "mock"). Só no desenvolvimento local. |
clockSkewSeconds | A diferença de relógio aceita; 60 por padrão. |
fetch | Um fetch seu, para o conjunto de chaves. |
jwksUrl, issuer | Só https://uloggd.com, ou uma origem de loopback nos testes. Deixe de fora. |
Uma recusa é um ArcadeTokenError com um code: malformed,
unsupported_header, unknown_key, bad_signature, wrong_type,
wrong_issuer, wrong_audience, expired, not_yet_valid, too_old,
nonce_mismatch, mock_refused, test_refused ou jwks_unavailable.
O verificador guarda as chaves por no máximo 5 minutos, digam o que disserem
os cabeçalhos da resposta, e as busca de novo diante de um kid desconhecido
no máximo uma vez por minuto. Sem chaves válidas, uma busca que falhou o faz
esperar 10 segundos antes de pedir de novo e, enquanto isso, ele recusa com
jwks_unavailable.
Verificar em qualquer linguagem
Qualquer biblioteca JOSE que deixe fixar o algoritmo serve, desde que cada regra abaixo seja conferida:
- Forma. No máximo 8192 caracteres, em três segmentos base64url separados por pontos. Decodifique os dois primeiros como objetos JSON.
- Cabeçalho. Exatamente
alg,typekid, e nada mais.algéES256: fixe isso no seu código, nunca deixe o token escolher.typéuloggd-arcade-id+jwt, ekidé uma string. Recusejku,jwk,x5u,x5c,crite qualquer outro membro. - Chave. Encontre o
kidno conjunto de chaves emhttps://uloggd.com/api/v1/arcade/jwks. Umkiddesconhecido pode provocar uma nova busca, no máximo uma vez por minuto. - O conjunto. Aceite só uma resposta
200, sem seguir redirecionamentos, de no máximo 64 KiB, que seja JSON com uma listakeys. Leia só as 20 primeiras entradas, e delas só as que têmktyEC,crvP-256,xeyde 43 caracteres base64url,algausente ouES256euseausente ousig. Para cadakidvale a primeira entrada válida. Depois de uma busca que falhou, espere 10 segundos antes de buscar de novo. - Assinatura. ECDSA sobre P-256 com SHA-256, sobre os bytes dos dois
primeiros segmentos como chegaram, unidos por um ponto. A assinatura são os
64 bytes crus de
re depoiss; recuse uma em DER, e converta se a sua biblioteca quiser DER. issé exatamentehttps://uloggd.com.audé exatamente o id do seu aplicativo.- Tempo.
iateexpsão inteiros, eexpvem depois deiat. Aceitando 60 segundos de diferença de relógio:expnão passou,iatnão está no futuro, eiattem no máximo 300 segundos. - Membros.
sub,jti,nicknameeiidsão strings. nonceé o que o seu servidor emitiu para esta sessão, sem uso anterior. Marque-o como usado.scopeincluiidentify.modeéliveoutest. Decida onde as suas partidas de teste podem entrar, e recusetestem todo o resto.- Repetição. Guarde cada
jtie cada nonce por mais tempo do que um token vive, e recuse uma repetição. Elimine duplicados porjtie nonce, nunca pelo texto do token: assinaturas ECDSA são maleáveis, então as mesmas informações podem chegar com outra assinatura.
Verificação em outras linguagens publica
um verificador completo em Python, que confere cada uma destas regras e foi
verificado contra o SDK de Node, com as regras dos tokens de eventos de dados,
os códigos de recusa e o que conferir quando você usa uma biblioteca de JWT.
Verificadores em Go e C# estão escritos no repositório do uloggd
(packages/arcade-sdk/examples, fora do pacote do npm), mas ainda não foram
verificados; a página publica cada um assim que ele passar.
O conjunto de chaves
GET https://uloggd.com/api/v1/arcade/jwks responde um JSON Web Key Set
público, com Cache-Control: public, max-age=300, stale-while-revalidate=60.
Ele lista a chave que assina hoje e qualquer chave prestes a assinar ou de
saída.
{
"keys": [
{
"kty": "EC",
"crv": "P-256",
"x": "...",
"y": "...",
"kid": "arc-2026-10",
"alg": "ES256",
"use": "sig"
}
]
}- Guarde-o em cache por no máximo 10 minutos. Quanto mais tempo um verificador guarda as chaves, mais tempo uma chave retirada continua valendo para ele.
- Busque-o só em
https://uloggd.com. Nunca num endereço*.squareweb.app: esses nomes são do provedor de hospedagem do uloggd, e um nome liberado lá pode ser tomado por outro cliente que sirva chaves próprias. - Fixar no seu verificador as chaves abaixo, em vez de buscá-las, é um uso suportado, desde que você siga o calendário de troca (veja Fixar as chaves).
Troca de chaves
As chaves se chamam arc-AAAA-MM e são trocadas a cada 6 meses:
- A chave nova entra no conjunto, só para verificar, e aparece nesta página no mesmo momento, com a data em que passa a assinar. Os verificadores com chaves fixas precisam dessas 48 horas.
- 48 horas depois ela passa a assinar.
- 24 horas depois disso, a chave antiga sai do conjunto.
Um verificador que busca o conjunto pelo menos a cada 10 minutos nem percebe. Um verificador com chaves fixas precisa acrescentar a chave nova dentro daquelas 48 horas.
Se uma chave for comprometida, ela sai do conjunto na hora, e todo desenvolvedor do Arcade recebe um e-mail.
Chaves públicas atuais
kid | Assina desde | Sai do conjunto |
|---|---|---|
arc-2026-10 | 2026-10-14 | sem data ainda |
A próxima troca está planejada para abril de 2027, com a chave
arc-2027-04. As datas exatas aparecem aqui pelo menos 7 dias antes.
A chave que assina hoje, como o conjunto de chaves a publica:
{
"kty": "EC",
"crv": "P-256",
"x": "43itCWOfjgyC_mP-7YI8Vg3wmHMC2bkPKlod1D1qYcA",
"y": "jey41Kgw1_imPbgSuhlhTHutWiZeY9jnoHf45jKd_UA",
"kid": "arc-2026-10",
"alg": "ES256",
"use": "sig"
}O conjunto inteiro, com qualquer chave prestes a assinar ou de saída, está
sempre em GET https://uloggd.com/api/v1/arcade/jwks.
Fixar as chaves
Buscando o conjunto, qualquer resposta que não seja JSON, como uma página de
desafio ou um erro, faz o SDK recusar com jwks_unavailable. Um verificador
que não quer depender dessa busca pode fixar as chaves acima: passe a opção
fetch de verifyIdentityToken devolvendo o conjunto publicado como uma
Response com status 200 e cache-control: max-age=300.
import { verifyIdentityToken } from "@uloggd/arcade-sdk/server";
// O conjunto como esta página o publica. Acrescente a chave nova assim que
// ela aparecer em "Chaves públicas atuais".
const PINNED_JWKS = JSON.stringify({
keys: [
{
kty: "EC",
crv: "P-256",
x: "43itCWOfjgyC_mP-7YI8Vg3wmHMC2bkPKlod1D1qYcA",
y: "jey41Kgw1_imPbgSuhlhTHutWiZeY9jnoHf45jKd_UA",
kid: "arc-2026-10",
alg: "ES256",
use: "sig",
},
],
});
const pinned = async () =>
new Response(PINNED_JWKS, {
status: 200,
headers: { "cache-control": "max-age=300" },
});
const player = await verifyIdentityToken(token, {
appId: process.env.ULOGGD_APP_ID,
nonce,
fetch: pinned,
});verifyEventToken aceita a mesma opção fetch. Siga a
troca de chaves: acrescente a
chave nova nas 48 horas entre ela aparecer aqui e passar a assinar, e retire a
antiga depois que ela sair do conjunto.