1 · El token de acceso
Un JWT: tres partes en base64url separadas por puntos — cabecera, contenido y firma EdDSA. No está
cifrado: cualquiera que lo tenga lo lee, y cualquiera que lo tenga lo usa. Sólo la firma impide
fabricarlo. Esta página lo decodifica pero no verifica la firma: la llave pública (JWKS) no se publica por el
borde de dev, y quien verifica es el gateway.
2 · El token de renovación
scope=renew: sólo sirve en POST /security/auth/renew, que devuelve un par
NUEVO. Rota en cada uso (el viejo deja de valer) y caduca a los 7 días sin usarse. auth vuelve a leer al usuario
en la base al renovar: una cuenta desactivada no renueva. El token nuevo ya no lleva amr.
3 · Dónde se guarda
Una página estática no tiene servidor que ponga cookies HttpOnly, así que las pone
JavaScript y JavaScript las puede leer. La defensa es otra: la política de seguridad del servidor
(script-src 'self', sin scripts en línea ni de terceros) y que ningún dato se inserte como HTML.
Las cookies son del host (sin Domain): simplest.instances… no ve esta sesión
aunque use los mismos nombres.
| dónde | clave | contenido | vida | para qué |
|---|
Atributos que escribe js/auth.js en cada cookie: Path=/; SameSite=Strict; Secure;
Max-Age=…. El navegador no deja leerlos de vuelta: sólo nombre y valor. Nunca se guardan la contraseña,
el código del correo ni el code de Google.
4 · Qué ve el borde con este token
GET /memberships/me con Authorization: Bearer …. El gateway verifica firma,
caducidad y scope; borra cualquier X-Subject que mande el cliente y lo vuelve a poner
desde sub. El servicio sólo ve esa cabecera.
5 · El token del stream
POST /security/auth/sse-token con el token de acceso devuelve otro, scope=sse,
600 s. Va en la URL (/sse?token=…) porque EventSource no puede mandar cabeceras; por eso
es aparte y sólo abre el stream.
6 · Ver como
No toca tu sesión: pide un SEGUNDO token (POST /impersonation/view-as/token), 300 s, con
sub = tú e impSub = el otro usuario. El gateway lo aplica: X-Subject = el
otro, X-Superuser = false, X-Acting-User = tú, y sólo acepta lecturas. Vive en
sessionStorage (sólo esta pestaña). Salir es tirarlo: tu token nunca cambió.
7 · Cómo se llegó aquí: la traza de este login
Cada llamada que hizo esta pestaña para entrar: método, ruta, estado, tiempo y el
requestId con el que se busca en la auditoría. Nunca un cuerpo.
| +ms | llamada | estado | qué pasa | requestId |
|---|
8 · Las cuatro maneras de entrar, lado a lado
| contraseña | código / enlace | Google | proveedor OIDC |
| qué pruebas | que sabes un secreto | que controlas el buzón | que Google te autenticó | que tu empresa te autenticó |
| llamadas | POST /login | POST /code/request → correo → POST /code/verify |
navegador → Google → vuelve con code → POST /google {code, redirectUri} |
navegador → /federated/{idp}/start → proveedor → callback de auth → vuelve con handoff → POST /handoff |
| qué guarda auth | hash PBKDF2 en users.user_credential | hash del código en users.login_code, 10 min, 5 intentos |
el sub de Google, ligado por correo verificado la primera vez | el sub del proveedor; state, nonce y PKCE 10 min en memoria |
| quién tiene el secreto | tú | nadie: el código muere al usarse | auth (secreto del cliente OAuth) | auth (secreto del cliente, en Secret Manager) |
| crea usuarios | no — sólo entran cuentas que Tagora ya dio de alta (AUTH_JIT_PROVISION=false) |
| error | 401 único para todo | la petición responde 200 para cualquier correo; el canje, 401 único | 401 account not registered | vuelve con ?error=… |
amr | password | code | google | handoff |
| termina en | LoginHandlers.issue(): el mismo par de tokens, firmado con la misma llave |
9 · Después del login: autenticación y autorización en cada petición
- Autenticación, en el gateway. Firma EdDSA contra el JWKS de auth,
exp, y que el
scope sirva para la ruta. Falla → 401. Un token de integración en una ruta de primera
parte, o al revés → 403 forbidden scope.
- Identidad. El gateway borra
X-Subject, X-Scope,
X-Superuser, X-View-As, X-Act-As y X-Acting-User que vengan del
cliente y los vuelve a escribir desde el token. Ningún servicio vuelve a mirar el token.
- PEP, en el servicio. Cada ruta está en un manifiesto (
route_manifest.tsv): qué
permiso, sobre qué tipo, con qué id. Ruta sin política → 403 no policy for route.
- PDP (mstar-authz → SpiceDB). ¿Tiene
user:<sub> el permiso sobre ese objeto?
No → 403. No se pudo preguntar → 503. Las listas se filtran con un solo CheckBulk: lo que
no puedes ver no aparece (un 200 más corto, no un error).
- Datos. Funciones
SECURITY DEFINER en Postgres; las escrituras registran quién las
hizo (y, bajo ver-como/actuar-como, acting_user_id).
El método de login no cambia nada de esto: el gateway, el PEP y el PDP no distinguen un token de
contraseña de uno de Google. Sólo la auditoría ve el amr. Un superusuario se salta el PEP; bajo
ver-como nunca lo es.