uloggdDesarrolladores
Portal de desarrolladoresuloggd
EmpezarRecursosArcade
Portal de desarrolladoresuloggdArcadeAlojamientoEl frameSDKIdentidadVerificación en otros lenguajesEventos de datosPruebasPolítica de revisiónDeberes de privacidadMotores

Identidad

Sabe quién está jugando, con un token de cinco minutos firmado por uloggd y verificado por tu servidor, sin conexión.

El cliente de un juego puede afirmar cualquier cosa sobre quién juega. Tu servidor necesita una prueba, y la recibe como un token de identidad: un JWT firmado por uloggd para un jugador, una aplicación y un nonce que emitió tu servidor, válido durante cinco minutos. Tu servidor lo verifica sin conexión, con las llaves públicas de uloggd. No hay API de uloggd que llamar ni secreto que guardar.

El flujo

  1. Tu servidor emite un nonce, lo ata a la sesión que recibirá el token y lo guarda.
  2. El juego llama a authorize({ scopes: ["identify"] }) una vez, y después a getIdentityToken({ nonce }).
  3. El juego envía el token a tu servidor.
  4. Tu servidor lo verifica, gasta el nonce y abre su propia sesión para el sub.

Verifica de nuevo en cada inicio de sesión, y mantén tus sesiones en 24 horas como máximo. Un jugador que revoca el acceso de tu juego deja de recibir tokens al instante, pero las sesiones que tu servidor ya abrió te toca cerrarlas a ti: el evento de datos grant_revoked te avisa de cuándo.

El token

header  { "alg": "ES256", "typ": "uloggd-arcade-id+jwt", "kid": "arc-2026-10" }
payload {
  "iss": "https://uloggd.com",
  "aud": "ID_DE_TU_APLICACION",
  "sub": "4f1c2b7e-...",   el id del jugador en tu juego
  "nickname": "Ada",
  "scope": "identify",
  "iid": "0b7e9a31-...",   la partida, como instance_id en la URL de apertura
  "mode": "live",          o "test"
  "nonce": "...",          el que emitió tu servidor
  "iat": 1791860000,
  "exp": 1791860300,       iat + 300
  "jti": "9d2e5f60-..."
}
  • sub es un uuid exclusivo de este jugador en este juego; otro juego ve uno distinto para la misma persona. Es el mismo de una partida a otra, y solo cambia cuando el jugador elige Olvidar este juego.
  • nickname es el apodo que el jugador eligió para tu juego. Puede cambiar, y el evento de datos nickname_changed te avisa.
  • mode es test cuando juegas un lanzamiento tuyo en modo de prueba, o tu URL de desarrollo. Un token así tiene el mismo iss y el mismo aud que cualquier otro, y ninguna información propia. Quien revisa juega en live, con un sub y un apodo nuevos en cada revisión.
  • Nunca vienen en él: el correo, los ids internos de uloggd, la fecha de nacimiento, la edad, la dirección IP.

Verificar en Node

import {
  verifyIdentityToken,
  ArcadeTokenError,
} from "@uloggd/arcade-sdk/server";

app.post("/session", express.json(), async (req, res) => {
  const nonce = takeNonce(req); // el nonce emitido para esta sesión; vale una 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;
  }
});

En un servidor CommonJS, require("@uloggd/arcade-sdk/server") da las mismas funciones.

Opción
appIdEl id de tu aplicación. Obligatorio.
nonceEl nonce que emitió tu servidor. Obligatorio.
allowTestModefalse rechaza los tokens del modo de prueba (mode: "test"). Por defecto se aceptan.
allowMockAcepta los tokens del simulador (iss: "mock"). Solo en desarrollo local.
clockSkewSecondsLa diferencia de reloj que se acepta; 60 por defecto.
fetchUn fetch tuyo, para el conjunto de llaves.
jwksUrl, issuerSolo https://uloggd.com, o un origen de loopback en las pruebas. Déjalos fuera.

Un rechazo es un ArcadeTokenError con un 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 o jwks_unavailable.

El verificador guarda las llaves 5 minutos como máximo, digan lo que digan las cabeceras de la respuesta, y las vuelve a pedir ante un kid desconocido como mucho una vez por minuto. Sin llaves vigentes, una petición fallida lo hace esperar 10 segundos antes de volver a pedirlas y, mientras tanto, rechaza con jwks_unavailable.

Verificar en cualquier lenguaje

Cualquier biblioteca JOSE que permita fijar el algoritmo sirve, siempre que se compruebe cada regla de abajo:

  1. Forma. Como mucho 8192 caracteres, en tres segmentos base64url separados por puntos. Decodifica los dos primeros como objetos JSON.
  2. Cabecera. Exactamente alg, typ y kid, y nada más. alg es ES256: fíjalo en tu código, nunca dejes que el token lo elija. typ es uloggd-arcade-id+jwt, y kid es una cadena. Rechaza jku, jwk, x5u, x5c, crit y cualquier otro miembro.
  3. Llave. Busca el kid en el conjunto de llaves de https://uloggd.com/api/v1/arcade/jwks. Un kid desconocido puede provocar una nueva descarga, como mucho una vez por minuto.
  4. El conjunto. Acepta solo una respuesta 200, sin seguir redirecciones, de como mucho 64 KiB, que sea JSON con una lista keys. Lee solo sus 20 primeras entradas, y de ellas solo las que tienen kty EC, crv P-256, x e y de 43 caracteres base64url, alg ausente o ES256 y use ausente o sig. Para cada kid vale la primera entrada válida. Tras una descarga fallida, espera 10 segundos antes de volver a descargarlo.
  5. Firma. ECDSA sobre P-256 con SHA-256, sobre los bytes de los dos primeros segmentos tal como llegaron, unidos por un punto. La firma son los 64 bytes en bruto de r y luego s; rechaza una en DER, y conviértela si tu biblioteca quiere DER.
  6. iss es exactamente https://uloggd.com.
  7. aud es exactamente el id de tu aplicación.
  8. Tiempo. iat y exp son enteros, y exp va después de iat. Aceptando 60 segundos de diferencia de reloj: exp no ha pasado, iat no está en el futuro, e iat tiene como mucho 300 segundos.
  9. Miembros. sub, jti, nickname e iid son cadenas.
  10. nonce es el que tu servidor emitió para esta sesión, sin usar antes. Márcalo como usado.
  11. scope incluye identify.
  12. mode es live o test. Decide dónde pueden entrar tus partidas de prueba, y rechaza test en todo lo demás.
  13. Repetición. Guarda cada jti y cada nonce durante más tiempo del que vive un token, y rechaza una repetición. Elimina duplicados por jti y nonce, nunca por el texto del token: las firmas ECDSA son maleables, así que los mismos datos pueden llegar con otra firma.

Verificación en otros lenguajes publica un verificador completo en Python, que comprueba cada una de estas reglas y está verificado frente al SDK de Node, con las reglas de los tokens de eventos de datos, los códigos de rechazo y lo que comprobar cuando usas una biblioteca de JWT. Los verificadores en Go y C# están escritos en el repositorio de uloggd (packages/arcade-sdk/examples, fuera del paquete de npm), pero aún no están verificados; la página publica cada uno en cuanto pase.

El conjunto de llaves

GET https://uloggd.com/api/v1/arcade/jwks responde un JSON Web Key Set público, con Cache-Control: public, max-age=300, stale-while-revalidate=60. Lista la llave que firma hoy y cualquier llave a punto de firmar o de salida.

{
  "keys": [
    {
      "kty": "EC",
      "crv": "P-256",
      "x": "...",
      "y": "...",
      "kid": "arc-2026-10",
      "alg": "ES256",
      "use": "sig"
    }
  ]
}
  • Guárdalo en caché 10 minutos como máximo. Cuanto más tiempo guarda las llaves un verificador, más tiempo sigue valiendo para él una llave retirada.
  • Descárgalo solo de https://uloggd.com. Nunca de una dirección *.squareweb.app: esos nombres son del proveedor de alojamiento de uloggd, y otro cliente puede tomar un nombre que quede libre allí y servir sus propias llaves.
  • Fijar en tu verificador las llaves de abajo, en lugar de descargarlas, es un uso admitido, siempre que sigas el calendario de cambio (consulta Fijar las llaves).

Cambio de llaves

Las llaves se llaman arc-AAAA-MM y se cambian cada 6 meses:

  1. La llave nueva entra en el conjunto, solo para verificar, y aparece en esta página en ese mismo momento, con la fecha en que empieza a firmar. Los verificadores con llaves fijas necesitan esas 48 horas.
  2. 48 horas después empieza a firmar.
  3. 24 horas después de eso, la llave antigua sale del conjunto.

Un verificador que descarga el conjunto al menos cada 10 minutos ni lo nota. Un verificador con llaves fijas debe añadir la llave nueva dentro de esas 48 horas.

Si una llave llegara a comprometerse, sale del conjunto al instante, y todo desarrollador de Arcade recibe un correo.

Llaves públicas actuales

kidFirma desdeSale del conjunto
arc-2026-102026-10-14sin fecha todavía

El próximo cambio está previsto para abril de 2027, con la llave arc-2027-04. Las fechas exactas se publican aquí al menos 7 días antes.

La llave que firma hoy, tal como la publica el conjunto de llaves:

{
  "kty": "EC",
  "crv": "P-256",
  "x": "43itCWOfjgyC_mP-7YI8Vg3wmHMC2bkPKlod1D1qYcA",
  "y": "jey41Kgw1_imPbgSuhlhTHutWiZeY9jnoHf45jKd_UA",
  "kid": "arc-2026-10",
  "alg": "ES256",
  "use": "sig"
}

El conjunto entero, con cualquier llave a punto de firmar o de salida, está siempre en GET https://uloggd.com/api/v1/arcade/jwks.

Fijar las llaves

Al descargar el conjunto, cualquier respuesta que no sea JSON, como una página de desafío o un error, hace que el SDK rechace con jwks_unavailable. Un verificador que prefiera no depender de esa descarga puede fijar las llaves de arriba: pasa a verifyIdentityToken una opción fetch que devuelva el conjunto publicado como una Response con estado 200 y cache-control: max-age=300.

import { verifyIdentityToken } from "@uloggd/arcade-sdk/server";

// El conjunto tal como lo publica esta página. Añade la llave nueva en cuanto
// aparezca en "Llaves públicas actuales".
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 acepta la misma opción fetch. Sigue el cambio de llaves: añade la llave nueva dentro de las 48 horas entre que aparece aquí y empieza a firmar, y quita la antigua cuando haya salido del conjunto.

SDK

@uloggd/arcade-sdk: el cliente, cada comando, evento, error y código de cierre, y el simulador.

Verificación en otros lenguajes

Verificadores completos de los tokens de identidad y de eventos de datos, con las mismas reglas y los mismos códigos de rechazo que el SDK para Node.

En esta página

El flujoEl tokenVerificar en NodeVerificar en cualquier lenguajeEl conjunto de llavesCambio de llavesLlaves públicas actualesFijar las llaves