La sesión con cookie¶
Por defecto, el login devuelve el access token y el refresh token en el cuerpo, y lo que haga el
frontend con ellos es cosa suya. Casi siempre acaba en localStorage, donde cualquier XSS se los
lleva. La sesión con cookie lo resuelve en la API: el refresh token viaja en una cookie que el
JavaScript del navegador no puede leer, y rota en cada uso.
Es lo que usa el cliente TypeScript, y lo que exige.
Activarla¶
{
"auth": {
"type": "database",
"config": { "session_management": true },
"session": {
"transport": "cookie",
"access_minutes": 10,
"idle_hours": 8,
"absolute_hours": 12
}
}
}
| Clave | Qué es | Por defecto |
|---|---|---|
transport |
body (lo de siempre) o cookie |
body |
access_minutes |
Lo que vale un access token | 10 |
idle_hours |
La inactividad máxima: lo que vale el refresh, y la cookie | 8 |
absolute_hours |
El límite de la sesión, que ningún refresco alarga | 12 |
Con los valores por defecto se inicia sesión una vez al día. Sin el bloque, todo sigue como antes.
Qué cambia en /auth¶
| Endpoint | Con la cookie |
|---|---|
POST /auth/{realm}/login |
Deja el refresh en la cookie. En el cuerpo, el access token, expires_in y session_expires_at; sin refresh_token |
POST /auth/refresh |
Sin cuerpo: lee la cookie, y la devuelve rotada |
POST /auth/logout |
Sin cuerpo: cierra la sesión y borra la cookie, siempre |
La cookie se llama tai_refresh, es HttpOnly, SameSite=Strict, va limitada a /auth —no sale
en ninguna petición de datos— y es Secure salvo en localhost.
Lo que la protege¶
- Rota en cada refresco. Presentar un refresh que ya se usó es que alguien tiene una copia: se
revoca la sesión entera y se responde
SESSION_INVALIDATEDconreason: "refresh_reused". Durante 10 segundos tras rotar, el anterior todavía vale, para que un reintento de red no cierre la sesión de un usuario legítimo. - Una sesión por usuario. Iniciar sesión en otro dispositivo cierra la anterior:
SESSION_INVALIDATEDconreason: "concurrent_login". - CSRF. Además del
SameSite=Strict,refreshylogoutexigen la cabeceraX-Tai-Client: 1(403 sin ella): una petición desde otro origen que la lleve obliga a un preflight que el CORS rechaza. - CORS. Con la cookie, la app no arranca con
ALLOWED_ORIGINS = ["*"], que es lo que dejatai-api initenconfig.py. Detrás del proxy del frontend, la lista queda vacía; si el frontend está en otro puerto, van sus orígenes exactos.
Lo que exige cada modo¶
session_management: true: la rotación se apoya en la sesión que guarda el schemaauth.- Un schema
authcon las columnasrefresh_generationyrefresh_rotated_aten la tablauser. Los creados con tai-sql 0.7.10 o posterior las traen; en uno anterior, se añaden a la claseUserdeschemas/auth.pyy se hacetai-sql push -s auth.
generate se niega, con la solución, si falta cualquiera de las dos.
PUBLIC_URL: la URL de la API tal como la ve el navegador, con su/apisi va detrás de un proxy.- El realm preparado: lo hace
tai-api rbac push(ver Keycloak).
La rotación, las duraciones y la sesión única las aplica Keycloak.
Desplegarla¶
La cookie solo viaja si el frontend y la API son el mismo sitio. En Azure, dos Web Apps no lo son. Ver Despliegue del frontend.