uloggdDesenvolvedores
Portal do desenvolvedoruloggd
ComeçarRecursosArcade
Portal do desenvolvedoruloggdArcadeHospedagemO frameSDKIdentidadeVerificação em outras linguagensEventos de dadosTestesPolítica de revisãoDeveres de privacidadeEngines

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

  1. O seu servidor emite um nonce, amarra-o à sessão que vai receber o token e o guarda.
  2. O jogo chama authorize({ scopes: ["identify"] }) uma vez, e depois getIdentityToken({ nonce }).
  3. O jogo manda o token ao seu servidor.
  4. 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 dados nickname_changed avisa.
  • mode é test quando você joga um lançamento seu no modo de teste, ou a sua URL de desenvolvimento. Um token assim tem o mesmo iss e o mesmo aud de qualquer outro, e nenhuma informação própria. Quem revisa joga em live, com um sub e 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
appIdO id do seu aplicativo. Obrigatório.
nonceO nonce que o seu servidor emitiu. Obrigatório.
allowTestModefalse recusa os tokens do modo de teste (mode: "test"). Por padrão eles são aceitos.
allowMockAceita os tokens do simulador (iss: "mock"). Só no desenvolvimento local.
clockSkewSecondsA diferença de relógio aceita; 60 por padrão.
fetchUm fetch seu, para o conjunto de chaves.
jwksUrl, issuerSó 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:

  1. Forma. No máximo 8192 caracteres, em três segmentos base64url separados por pontos. Decodifique os dois primeiros como objetos JSON.
  2. Cabeçalho. Exatamente alg, typ e kid, e nada mais. alg é ES256: fixe isso no seu código, nunca deixe o token escolher. typ é uloggd-arcade-id+jwt, e kid é uma string. Recuse jku, jwk, x5u, x5c, crit e qualquer outro membro.
  3. Chave. Encontre o kid no conjunto de chaves em https://uloggd.com/api/v1/arcade/jwks. Um kid desconhecido pode provocar uma nova busca, no máximo uma vez por minuto.
  4. O conjunto. Aceite só uma resposta 200, sem seguir redirecionamentos, de no máximo 64 KiB, que seja JSON com uma lista keys. Leia só as 20 primeiras entradas, e delas só as que têm kty EC, crv P-256, x e y de 43 caracteres base64url, alg ausente ou ES256 e use ausente ou sig. Para cada kid vale a primeira entrada válida. Depois de uma busca que falhou, espere 10 segundos antes de buscar de novo.
  5. 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 r e depois s; recuse uma em DER, e converta se a sua biblioteca quiser DER.
  6. iss é exatamente https://uloggd.com.
  7. aud é exatamente o id do seu aplicativo.
  8. Tempo. iat e exp são inteiros, e exp vem depois de iat. Aceitando 60 segundos de diferença de relógio: exp não passou, iat não está no futuro, e iat tem no máximo 300 segundos.
  9. Membros. sub, jti, nickname e iid são strings.
  10. nonce é o que o seu servidor emitiu para esta sessão, sem uso anterior. Marque-o como usado.
  11. scope inclui identify.
  12. mode é live ou test. Decida onde as suas partidas de teste podem entrar, e recuse test em todo o resto.
  13. Repetição. Guarde cada jti e cada nonce por mais tempo do que um token vive, e recuse uma repetição. Elimine duplicados por jti e 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:

  1. 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.
  2. 48 horas depois ela passa a assinar.
  3. 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

kidAssina desdeSai do conjunto
arc-2026-102026-10-14sem 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.

SDK

@uloggd/arcade-sdk: o cliente, cada comando, evento, erro e código de fechamento, e o simulador.

Verificação em outras linguagens

Verificadores completos dos tokens de identidade e de eventos de dados, com as mesmas regras e os mesmos códigos de recusa do SDK para Node.

Nesta página

O fluxoO tokenVerificar em NodeVerificar em qualquer linguagemO conjunto de chavesTroca de chavesChaves públicas atuaisFixar as chaves