====== 08 - Login con JWT ======
La tienda ya vende con transacciones; le falta saber **quién compra y quién administra**. Este hito aplica el [[temario:06-login-jwt|tema 06 — Login con JWT]]: el módulo **users** con registro y login, el par de tokens — access **JWT** de vida corta y refresh **opaco** persistido — y la validación stateless en un filtro propio. La autorización declarativa llega en el hito siguiente.
^ Pieza ^ Cambio en este hito ^
| Contrato | v1.0.0: registro y login; el pedido toma la identidad del token |
| Estructura | Módulo **users** nuevo, que nace ya con JPA |
| Mecanismo | Access **JWT** + refresh opaco con rotación; filtro Servlet propio |
===== 1. El contrato evoluciona =====
- Ampliar ''openapi.yaml'' a v1.0.0 — cambio **breaking**, por eso salta de versión mayor:
^ Operación ^ Petición ^ Respuestas ^
| Registro | ''POST /api/users'' · ''UserRequest'' | 201 · 400 · 409 |
| Login | ''POST /api/auth/login'' · ''LoginRequest'' | 200 · 401 |
| Refresh | ''POST /api/auth/refresh'' · ''RefreshToken'' | 200 · 401 |
| Logout | ''POST /api/auth/logout'' · ''RefreshToken'' | 204 · 401 |
| Mis pedidos | ''GET /api/me/orders'' | 200 · 401 |
- Registro: ''name'', ''email'' y ''password'' (mínimo 8). Login y refresh devuelven ''TokenResponse'': ''accessToken'', ''refreshToken'' y ''expiresIn''. El logout revoca el refresh que recibe.
- El cambio breaking: ''OrderRequest'' **pierde ''userId''** — la identidad del comprador ya no viaja en el cuerpo, sino en el token. Los pedidos de prueba de los hitos anteriores se limpian: su ''userId'' era provisional.
===== 2. El módulo users =====
* Módulo nuevo en la verificación, con la estructura aprendida en el [[proyecto:07-spring-jpa|hito 07]]: interfaces de repositorio en ''domain'' y mecanismo JPA en ''persistence''.
* Tablas ''users'' y ''refresh_tokens'' de la [[proyecto:02-especificacion|especificación]]: ampliar el ''init.sql'' y reiniciar el volumen de MariaDB. La semilla trae un usuario ''ADMIN'' con su hash; el registro crea siempre ''USER''.
* Contraseñas **hash**, nunca en claro. El dominio no conoce el mecanismo de hashing: define lo que necesita y la composición aporta la implementación, de la forma que decida cada quien; en el [[proyecto:09-spring-security|hito 09]] pasará a la maquinaria de **Spring Security** sin tocar el dominio.
* El login devuelve el par de tokens: **access JWT** de vida corta, firmado en el servidor, y **refresh opaco** — una cadena aleatoria sin significado, persistida por su hash con su caducidad y su capacidad de revocación. El refresh se usa una vez: al usarlo se revoca y se entrega uno nuevo (**rotación**); el logout revoca el refresh recibido. Los claims del access token bastan para que el resto de la aplicación reconozca la identidad — id y rol — sin conocer nada del módulo users.
El access token es **stateless**, pero el par no: el refresh rota al usarse y se revoca en el logout. La tabla ''refresh_tokens'' de la especificación existe por eso.
===== 3. Autenticación en el filtro =====
* Aún sin **Spring Security**, la protección es un filtro Servlet propio, como en el [[temario:06-login-jwt|tema 06]]: extrae el token de la cabecera, valida firma y caducidad, deja la identidad a la vista de los controladores y responde 401 en las rutas que la exigen. Los filtros no pasan por los manejadores de la presentación: el 401 lo escribe el filtro con el formato del contrato.
* Las rutas protegidas son todavía una lista gestionada a mano en el filtro.
* Con la identidad en marcha, ''GET /api/me/orders'' devuelve solo los pedidos del comprador: la identidad viene del token, no de un parámetro.
La lista de rutas «a mano» está a propósito: muestra el límite del enfoque. En el [[proyecto:09-spring-security|hito 09]] la sustituye la cadena declarativa de **Spring Security**, sin cambiar el flujo de tokens.
===== 4. Pruebas =====
* **Registro**: usuario correcto → 201; email repetido → 409; contraseña corta → 400.
* **Login**: credenciales correctas → 200 con el par de tokens; usuario o contraseña incorrectos → 401.
* **Refresh**: correcto → par nuevo y el viejo revocado; reusar el viejo → 401; refresh caducado → 401.
* **Logout**: revoca el refresh; usarlo después → 401.
* **Filtro**: ruta protegida sin token → 401; con token caducado o mal firmado → 401; rutas públicas sin token pasan; la identidad llega al controlador con id y rol.
* **Mis pedidos**: devuelve solo los del comprador del token.
===== Comprobación =====
* El flujo completo corre de punta a punta: registro → login → pedido → mis pedidos → cancelación → refresh → logout.
* El contrato está en v1.0.0 y el Swagger refleja las operaciones nuevas; ''OrderRequest'' ya no lleva ''userId''.
* La verificación de límites sigue verde con el tercer módulo.
* Commit y tag ''hito-08''.
**Siguiente hito:** [[proyecto:09-spring-security|hito 09 — Integración con Spring Security]].