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
- Tu servidor emite un nonce, lo ata a la sesión que recibirá el token y lo guarda.
- El juego llama a
authorize({ scopes: ["identify"] })una vez, y después agetIdentityToken({ nonce }). - El juego envía el token a tu servidor.
- 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-..."
}subes 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.nicknamees el apodo que el jugador eligió para tu juego. Puede cambiar, y el evento de datosnickname_changedte avisa.modeestestcuando juegas un lanzamiento tuyo en modo de prueba, o tu URL de desarrollo. Un token así tiene el mismoissy el mismoaudque cualquier otro, y ninguna información propia. Quien revisa juega enlive, con unsuby 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 | |
|---|---|
appId | El id de tu aplicación. Obligatorio. |
nonce | El nonce que emitió tu servidor. Obligatorio. |
allowTestMode | false rechaza los tokens del modo de prueba (mode: "test"). Por defecto se aceptan. |
allowMock | Acepta los tokens del simulador (iss: "mock"). Solo en desarrollo local. |
clockSkewSeconds | La diferencia de reloj que se acepta; 60 por defecto. |
fetch | Un fetch tuyo, para el conjunto de llaves. |
jwksUrl, issuer | Solo 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:
- Forma. Como mucho 8192 caracteres, en tres segmentos base64url separados por puntos. Decodifica los dos primeros como objetos JSON.
- Cabecera. Exactamente
alg,typykid, y nada más.algesES256: fíjalo en tu código, nunca dejes que el token lo elija.typesuloggd-arcade-id+jwt, ykides una cadena. Rechazajku,jwk,x5u,x5c,crity cualquier otro miembro. - Llave. Busca el
kiden el conjunto de llaves dehttps://uloggd.com/api/v1/arcade/jwks. Unkiddesconocido puede provocar una nueva descarga, como mucho una vez por minuto. - El conjunto. Acepta solo una respuesta
200, sin seguir redirecciones, de como mucho 64 KiB, que sea JSON con una listakeys. Lee solo sus 20 primeras entradas, y de ellas solo las que tienenktyEC,crvP-256,xeyde 43 caracteres base64url,algausente oES256yuseausente osig. Para cadakidvale la primera entrada válida. Tras una descarga fallida, espera 10 segundos antes de volver a descargarlo. - 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
ry luegos; rechaza una en DER, y conviértela si tu biblioteca quiere DER. isses exactamentehttps://uloggd.com.audes exactamente el id de tu aplicación.- Tiempo.
iatyexpson enteros, yexpva después deiat. Aceptando 60 segundos de diferencia de reloj:expno ha pasado,iatno está en el futuro, eiattiene como mucho 300 segundos. - Miembros.
sub,jti,nicknameeiidson cadenas. noncees el que tu servidor emitió para esta sesión, sin usar antes. Márcalo como usado.scopeincluyeidentify.modeesliveotest. Decide dónde pueden entrar tus partidas de prueba, y rechazatesten todo lo demás.- Repetición. Guarda cada
jtiy cada nonce durante más tiempo del que vive un token, y rechaza una repetición. Elimina duplicados porjtiy 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:
- 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.
- 48 horas después empieza a firmar.
- 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
kid | Firma desde | Sale del conjunto |
|---|---|---|
arc-2026-10 | 2026-10-14 | sin 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.