Vista generada de dec-0113: Modelo de tokens de identity-svc
docs/decisions/dec-0113-identity-token-model.mdVista generada desde
docs/decisions/dec-0113-identity-token-model.md. No se edita a mano:bun run docs:genla regenera ybun run docs:checkfalla si difiere. El estado aquí es el del model: si discrepa con otra página, manda el model.
| Campo | Valor |
|---|---|
| Estado | PROPOSED |
| Fecha | 2026-09-28 |
| Fichero | docs/decisions/dec-0113-identity-token-model.md |
Por qué importa (del frontmatter del ADR):
Formato de credenciales que emite identity-svc y que verifican todos los servicios (qry.identity.session, JWKS) y clientes (web, Apple). track/identity#01 D3.
Nodos del roadmap que lo citan en refs: ninguno.
Páginas de la documentación que lo citan: API keys (especificado), Conectar la TV y el CLI (especificado), Iniciar sesión (especificado)
Leído de docs/decisions/dec-0113-identity-token-model.md, el fichero canónico.
AskUserQuestion a waxin. Implementado en
apps/identity-svc como opción recomendada porque sus piezas (issuer propio + JWKS + store de
sesiones) son las que cualquier alternativa razonable también necesita; lo que el lock fija es
el formato y las duraciones.ISessionStore/ITokenIssuer), dec-0087 (identity
authoritative sobre credenciales), dec-0006 (Valkey para estado efímero).El OP (PocketID) autentica personas; Styx necesita credenciales propias para sus servicios:
el sub del OP no es un id de Styx, el OP no conoce las sesiones de Styx y un servicio no debe
llamar al OP en cada request. Hay que decidir qué emite identity-svc y cómo lo verifican los demás.
| A. JWT propio + refresh opaco rotatorio (recomendada) | B. Reenviar el access token del OP | C. Sesión opaca, todo por qry.identity.session | |
|---|---|---|---|
| Verificación en servicios | offline (JWKS) o revocation-aware por NATS, a elegir por el consumer | contra el JWKS del OP; el sub es del OP | siempre un round-trip NATS |
| Revocación | inmediata vía qry.identity.session; ventana ≤ TTL del access en verificación offline | depende del OP | inmediata |
| Cambiar de OP | no cambia ids ni tokens de Styx | rompe todos los ids | no cambia |
| Coste | clave de firma a custodiar | ninguno propio | identity-svc en el hot path de todo request |
alg: EdDSA (Ed25519), typ: at+jwt (RFC 9068), TTL 15 min.
Claims: iss = URL pública de identity-svc, aud = styx, sub = actorId de Styx, sid =
sessionId, name opcional, iat, exp, jti. Clave pública en /.well-known/jwks.json con
kid = thumbprint RFC 7638.<sessionId>.<secreto 256 bit>, con sessionId UUID (cualquier otra
forma se rechaza como IDENTITY_TOKEN_INVALID sin consultar Valkey); Valkey guarda sólo
SHA-256(secreto). Rotación en cada uso (script Lua atómico). Presentar uno ya rotado revoca
la sesión entera (reuse detection, OAuth 2.0 Security BCP RFC 9700 §4.14), con la excepción
de la ventana de gracia:
IDENTITY_REFRESH_GRACE_S, 10 s por defecto. Sólo cuenta el token
inmediatamente anterior al vigente y sólo durante los N segundos siguientes a su rotación.
Dentro de la ventana emite un access token sin rotar y sin revocar (el vigente sigue
siendo el mismo; la respuesta no trae refresh token nuevo). Fuera de la ventana, o con
cualquier token más antiguo, se aplica la reuse detection: IDENTITY_REFRESH_TOKEN_REUSED
y la sesión entera revocada.qry.identity.session) sin que la sesión se revoque; el robo
sólo se detecta si lo usa después de la ventana. Es la misma concesión que documentan los
emisores que rotan refresh tokens con "reuse interval" (Auth0, Okta).IDENTITY_REFRESH_GRACE_S=0 elimina la ventana (el anterior es reuse
desde el primer milisegundo). El valor admite cualquier entero ≥ 0.HttpOnly; SameSite=Strict; Path=/auth
(Secure si la URL pública es https). El access token se pide con POST /auth/refresh y vive
en memoria del cliente, nunca en storage persistente.IDENTITY_SIGNING_KEY (PKCS#8 PEM Ed25519) por entorno; sin clave el
servicio no arranca. Rotación con varios kid publicados = siguiente paso, fuera de este lock.0 = sin ventana), nombres de cookie, y si un consumer verifica offline o por NATS.0
(reuse detection estricta, a costa de esos falsos positivos).