Ejercicio 1 — Login
1-2. Flujo correcto: login → {access, refresh}; API con Bearer → 200; sin header → 401 credentials_not_provided; expirado → 401 token_not_valid (SimpleJWT responde 401 con código en el body — tu frontend lo usa para refrescar en silencio y reintentar una vez).
- Tras
refresh, el par viejo queda en blacklist (BLACKLIST_AFTER_ROTATION): el refresh viejo da 401. Esto es lo que hace el logout real (Ej. 5).
Ejercicio 2 — Anatomía
- Claims por defecto:
token_type,exp,iat,jti,user_id. Sin email (bien: mínimo necesario). Datos de pago JAMÁS: el payload es base64 legible por cualquiera con el token — la firma protege integridad, no confidencialidad. - Un carácter cambiado → 401
token_not_valid(firma inválida: el HMAC del payload no coincide). - Si el rol viajara en el payload y alguien lo editara a ADMIN, la firma no revalidaría (el HMAC cambiaría): el servidor rechaza. El riesgo real del rol en el token no es la edición sino la caducidad de la información: rol cambiado en BD sigue vivo en tokens emitidos hasta expirar — por eso el rol se lee de BD en cada request, no del token.
Ejercicio 3 — Dónde vive
- localStorage: el XSS lee el token completo y lo exporta (compromiso total hasta expirar). Cookie HttpOnly:
document.cookieno la muestra — el XSS puede USAR la sesión desde el navegador víctima mientras vive, pero no exportar el token ni persistir el acceso tras cerrar. Memoria + refresh HttpOnly: roba el access vivo solo; el refresh (7 días) está protegido. - SameSite=Lax bloquea el envío de la cookie en POST cross-site (curl con Origin falso NO es navegador: no ejecuta SameSite — la protección es del navegador; el atacante real es un navegador del usuario, y ahí Lax corta). El CSRF token de Django queda para el admin/formularios clásicos.
- Resumen: access 10 min en memoria; refresh 7d en cookie HttpOnly rotada + blacklist; móvil: keychain del SO.
Ejercicio 4 — ADR-0008 (esqueleto)
Contexto: web SPA + móvil sobre la misma API; pagos revocables; cumplimiento. Decisión: JWT access corto (10m) + refresh rotado (7d) con blacklist; refresh en cookie HttpOnly SameSite=Lax en web, keychain en móvil. Alternativas: sesión de Django única (fricción móvil, escala de estado), JWT largo en localStorage (XSS total). Consecuencias: infraestructura de blacklist (Redis/BD), logout = blacklist, rotación obligatoria, reuso de refresh detectado = revocar familia (detección de robo).
Ejercicio 5 — Logout real
- Blacklist del refresh:
refresh.blacklist()(o endpoint de SimpleJWT TokenBlacklist). El usuario ya no consigue access nuevos: logout efectivo. - El access vivo (≤10 min) sigue válido: aceptable en la mayoría de threat models (ventana corta). Si no: blacklist por
jtiverificada en cada request — pagas una consulta (Redis) por request: vuelve parte del estado que el JWT evitaba. Decisión consciente: qué ventana de exposición aceptas.
Resumen del profesor
- El estado no desaparece con JWT: se mueve (blacklist, rotación). La pregunta es qué revocas y cómo de rápido.
- Access corto + refresh rotado HttpOnly + blacklist: el patrón por defecto defendible en 2026.
- JWT legible ≠ secreto; firma ≠ cifrado. Payload mínimo.
- El rol y los datos sensibles se consultan en BD por request; el token solo identifica.
Después: Lección 19 — OAuth2 y OpenID Connect.