Login y sesiones
Login y sesiones
Section titled “Login y sesiones”Esta pagina explica como debe funcionar hoy el login de MedSync con autenticacion basada en cookies. El objetivo es que cualquier persona de producto, soporte, QA, backend, frontend o infraestructura pueda entender que pasa cuando un usuario entra al sistema, como se mantiene la sesion, como se cierra, que controles de seguridad existen y que falta validar antes de declarar production-ready.
La idea central cambio respecto a la documentacion anterior:
El navegador ya no debe manejar access token ni refresh token como datos legibles por JavaScript. Esos tokens viajan en cookies
HttpOnly. El frontend hidrata el usuario llamando al backend, no decodificando un JWT guardado en storage.
Estado actual
Section titled “Estado actual”El modelo de autenticacion por cookies ya esta implementado y probado localmente con Playwright. En desarrollo local normal se usa por default mediante los scripts npm run dev del backend y npm run serve del frontend. En production-like y produccion sigue detras de configuracion explicita y validaciones de rollout. Eso significa:
- El destino de seguridad aprobado es autenticacion basada en cookies.
- Local normal usa cookies D2 para evitar volver al contrato legacy por accidente.
- Legacy queda solo como opt-in local con
npm run dev:legacyynpm run serve:legacy. - El sistema no esta marcado como production-ready.
- El plan de rollout/rollback ya esta documentado.
- La siguiente validacion real debe hacerse en un ambiente production-like con HTTPS y dominio real.
Si solo necesitas levantar el sistema local y confirmar que no estas usando legacy, usa primero Local D2 cookies para junior y luego Troubleshooting local D2.
Regla rapida para desarrollo local:
Backend: npm run devFrontend: npm run serveNetwork: no debe existir AuthorizationCookies: medsync_at_dev, medsync_rt_dev, medsync_csrf_devSi ves Authorization en una peticion normal del frontend, lo mas probable es que estes usando legacy, una terminal vieja o residuos del navegador. No lo tomes como comportamiento normal de D2.
En local se valido que:
- Las cookies auth locales
medsync_at_devymedsync_rt_devsonHttpOnly. document.cookieno expone access/refresh.localStorageysessionStorageno contienen tokens.- Network del navegador no envia
Authorization. - Refresh funciona con cookie y body
{}. - Logout limpia cookies.
- Super Admin close invalida la sesion afectada.
- Tenant restricted y usuario inactivo quedan bloqueados en login sin cookies.
Lo que falta para production-ready:
- Repetir browser QA en HTTPS/domain real.
- Confirmar cookies
Secure=true. - Confirmar cookies
__Host-*si la topologia lo permite. - Validar reset password invalidation end-to-end con SMTP sandbox o humano autorizado.
- Validar backup/restore, observabilidad y rollback drill.
Modelo mental rapido
Section titled “Modelo mental rapido”| Antes / legacy | Ahora / cookies HttpOnly |
|---|---|
Frontend recibia token y a veces refresh_token en JSON. | Backend setea access/refresh en cookies HttpOnly. |
Axios enviaba Authorization. | Axios usa withCredentials y no envia Authorization para el navegador. |
| Store/router decodificaban JWT desde storage. | Frontend llama /api/auth/session para hidratar usuario, rol, permisos y tenant. |
| Refresh token podia vivir en storage cliente. | Refresh token vive en cookie HttpOnly y DB conserva hash/rotacion/reuse detection. |
| CSRF no era central porque auth iba por header. | CSRF es obligatorio porque el navegador adjunta cookies automaticamente. |
| Logout limpiaba storage y avisaba al backend. | Logout revoca servidor y expira cookies; frontend limpia runtime y residuos legacy. |
Flujo principal de login
Section titled “Flujo principal de login”- Usuario abre
/auth/login. - Frontend pide
GET /api/auth/csrf. - Backend entrega token CSRF y setea cookie CSRF no
HttpOnly. - Usuario escribe telefono y password.
- Frontend llama
POST /api/auth/logincon:- credenciales;
X-CSRF-Token;withCredentials=true.
- Backend valida payload, rate limit, usuario, password, tenant, CSRF y Origin/Referer.
- Si el usuario puede entrar, backend crea sesion server-side.
- Backend crea access token corto y refresh token v2.
- Backend setea cookies:
- access cookie
HttpOnly; - refresh cookie
HttpOnly; - CSRF cookie visible para JS.
- access cookie
- Backend responde sin access token y sin refresh token en JSON.
- Frontend llama
GET /api/auth/session. - Backend devuelve usuario, rol, permisos, business y tenantOperational sin tokens.
- Store/Vuex guarda ese contexto solo en runtime.
- Router redirige segun rol, permisos y estado operacional.
flowchart TD A["Usuario abre /auth/login"] --> B["GET /api/auth/csrf"] B --> C["Cookie CSRF + token para header"] C --> D["POST /api/auth/login con X-CSRF-Token"] D --> E["Backend valida credenciales, CSRF, Origin y tenant"] E --> F{"Puede entrar?"} F -- "No" --> G["Error generico; sin sesion ni cookies auth"] F -- "Si" --> H["Crear session + access + refresh v2"] H --> I["Set-Cookie HttpOnly access/refresh"] I --> J["GET /api/auth/session"] J --> K["Frontend hidrata runtime Vuex"] K --> L["Ruta protegida sin Authorization header"]Cookies de autenticacion
Section titled “Cookies de autenticacion”En production-like, la configuracion objetivo es:
| Cookie | Uso | HttpOnly | Secure | SameSite | Path | Domain |
|---|---|---|---|---|---|---|
__Host-medsync_at | Access token corto | Si | Si | Lax | / | Ninguno |
__Host-medsync_rt | Refresh token v2 raw | Si | Si | Lax | / | Ninguno |
__Host-medsync_csrf | CSRF double-submit | No | Si | Lax | / | Ninguno |
Reglas importantes:
- Access y refresh deben ser
HttpOnly; JavaScript no debe leerlos. - En production-like siempre deben usar
Secure=true. - Si se usa prefijo
__Host-, no puede existirDomain. Path=/es obligatorio para__Host-*y tambien ayuda a limpiar cookies correctamente.- La cookie CSRF no es
HttpOnlyporque el frontend necesita copiar el token al header. - Local HTTP puede usar nombres sin
__Host-ySecure=false, pero eso no cuenta como production-like.
Con cookies, el navegador envia credenciales automaticamente. Por eso este modelo necesita CSRF.
El modelo usado es signed double-submit:
- Backend emite una cookie CSRF legible por JavaScript.
- Frontend manda el mismo valor en
X-CSRF-Token. - Backend valida cookie, header, firma, expiracion y Origin/Referer.
Requests que deben llevar CSRF:
POST /api/auth/login.POST /api/auth/refreshToken.PUT /api/auth/closeo logout equivalente.POST,PUT,PATCH,DELETEprotegidos.- Forgot password public endpoints si el flujo de cookies los protege con bootstrap pre-auth.
Errores esperados:
| Caso | Resultado esperado |
|---|---|
| Falta header CSRF | 403. |
| Falta cookie CSRF | 403. |
| Cookie/header no coinciden | 403. |
| Firma invalida | 403. |
| Origin no permitido | 403. |
| Token valido | La request llega al controller o siguiente middleware. |
La autenticacion por cookies necesita CORS con credenciales porque frontend y API viven en dominios distintos:
Frontend: https://app.<dominio>API: https://api.<dominio>Reglas:
CORS_CREDENTIALS=true.CORS_ALLOWED_ORIGINS=https://app.<dominio>.- Nunca usar
*con credentials. - Preflight debe permitir
X-CSRF-Token. - Origenes no permitidos deben quedar fuera, sin credentials.
Vary: Origindebe estar presente cuando se refleja el origin.
Session hydration
Section titled “Session hydration”El frontend no debe decodificar JWT para saber quien es el usuario.
La fuente de verdad de la sesion en este modelo es:
GET /api/auth/sessionEse endpoint devuelve:
authenticated.userminimo.role.permissions.business.tenantOperational.- estado de sesion si aplica.
Nunca debe devolver:
- access token;
- refresh token;
- token hash;
- fingerprint completo;
- password;
- OTP;
- secretos CSRF internos.
Esto permite que reload, nueva pestana y arranque de la app funcionen sin storage persistente de tokens.
Donde vive cada dato
Section titled “Donde vive cada dato”| Dato | Donde debe vivir | Puede persistir largo plazo? |
|---|---|---|
| Access token | Cookie HttpOnly | No. |
| Refresh token | Cookie HttpOnly + hash/metadata server-side | No en JS. |
| CSRF token | Cookie no HttpOnly y runtime frontend | Solo como token no autenticante. |
| Usuario/rol/permisos | Vuex/runtime tras /api/auth/session | No. |
| Business/tenantOperational | Vuex/runtime tras /api/auth/session | No. |
| Remembered identifier | medsync.auth.rememberedIdentifier.v1 | Si, es la unica excepcion auth-related. |
| Password/OTP | Solo memoria/formulario temporal | No. |
La clave permitida medsync.auth.rememberedIdentifier.v1 guarda solo un identificador de conveniencia, por ejemplo telefono recordado. No autentica, no autoriza y no debe contener tokens.
Endpoints principales
Section titled “Endpoints principales”| Endpoint | Metodo | Uso en autenticacion por cookies |
|---|---|---|
/api/auth/csrf | GET | Entrega token CSRF y cookie CSRF. |
/api/auth/login | POST | Valida credenciales y setea cookies auth; respuesta tokenless. |
/api/auth/session | GET | Hidrata usuario, rol, permisos y tenant sin tokens. |
/api/auth/refreshToken | POST | Lee refresh cookie, rota refresh token v2 y renueva cookies. |
/api/auth/close | PUT | Cierra sesion actual, revoca servidor y expira cookies. |
/api/auth/forze-close | PUT | Cierre forzado legacy/protegido; debe mantenerse seguro. |
/api/auth/sendOtp | POST | Inicia recuperacion de password. |
/api/auth/validateOtp | POST | Valida codigo OTP/challenge. |
/api/auth/resetPassword | PUT | Cambia password e invalida sesiones/refresh tokens. |
Refresh token v2
Section titled “Refresh token v2”El refresh token permite renovar el access token sin pedir password otra vez.
En este modelo:
- Frontend llama
POST /api/auth/refreshToken. - El body del navegador debe ser
{}. - El refresh token real se lee desde cookie
HttpOnly. - Backend valida el refresh token v2.
- Backend rota el refresh token.
- Backend setea nuevas cookies.
- La respuesta no incluye access token ni refresh token.
- Frontend llama o conserva
/api/auth/sessionpara seguir hidratado.
Controles esperados:
- hash/firma server-side;
- binding a
id_session; - rotation;
- family;
- reuse detection;
- revocacion por logout, Super Admin close y reset password.
Si un refresh viejo se reutiliza, debe fallar y disparar el alcance de revocacion configurado.
Logout
Section titled “Logout”Logout debe limpiar los dos lados: servidor y navegador.
Backend:
- revoca la sesion;
- revoca refresh tokens asociados;
- expira access cookie;
- expira refresh cookie;
- expira o rota CSRF cookie segun contrato.
Frontend:
- limpia Vuex/runtime;
- limpia CSRF runtime;
- limpia residuos legacy de storage;
- redirige a
/auth/login; - debe hacer limpieza local aunque la llamada de logout falle.
401 y sesiones cerradas
Section titled “401 y sesiones cerradas”Un 401 puede ocurrir porque:
- access cookie expiro;
- access cookie falta;
- sesion fue cerrada;
- Super Admin cerro la sesion;
- reset password invalido sesiones;
- refresh fue revocado o reutilizado;
- backend ya no reconoce
id_session.
Cuando ocurre, el frontend debe:
- limpiar runtime;
- limpiar storage auth legacy;
- olvidar CSRF runtime;
- evitar loops con
/api/auth/sessiony/api/auth/csrf; - mandar al login cuando aplique.
Super Admin close session
Section titled “Super Admin close session”Super Admin puede cerrar sesiones de otros usuarios. Ese flujo es importante para respuesta a incidentes.
Comportamiento esperado:
- Super Admin ejecuta cierre individual o masivo.
- Backend revoca la sesion afectada.
- Backend revoca refresh tokens relacionados.
- El usuario afectado recibe
401en el siguiente request. - Frontend limpia runtime/storage y vuelve al login.
La prueba local con Playwright valido el caso individual: el usuario QA afectado quedo en 401 despues del cierre.
Reset password invalidation
Section titled “Reset password invalidation”Reset password no debe cambiar solo la contrasena. Tambien debe invalidar sesiones y refresh tokens previos.
Validacion obligatoria antes de production-ready:
- Usuario QA inicia sesion.
- Se confirma que access/refresh estan activos.
- Se ejecuta forgot/reset password con SMTP sandbox o humano autorizado.
- El access viejo falla.
- El refresh viejo falla.
- Password anterior falla.
- Password nueva funciona.
- Frontend limpia estado tras
401.
Reglas:
- No imprimir OTP.
- No imprimir passwords.
- No imprimir tokens.
- No inventar credenciales.
- Si no hay SMTP sandbox, documentarlo como pendiente humano.
Tenant restricted y usuario inactivo
Section titled “Tenant restricted y usuario inactivo”La decision CTO/Product para MVP/productivo inicial esta cerrada:
- Tenant restricted/suspended/inactive en login: hard-block.
- Usuario inactivo/bloqueado en login: hard-block.
- No se emite sesion.
- No se emite access cookie.
- No se emite refresh cookie.
- No se expone PHI.
- Se mantiene error generico.
Esto es intencional. No es un bug.
La ruta /tenant-restricted se conserva para otro caso: cuando ya existia una sesion y el tenant cambia de estado despues. Por ejemplo:
- usuario estaba logueado;
- tenant pasa a suspendido;
/api/auth/sessiondetectatenantOperational.allowed=false;- frontend redirige a
/tenant-restricted.
No se implementa ahora login limitado para billing/self-service. Ese tema queda como backlog futuro:
Limited restricted tenant session for billing/self-service UXRoles y permisos
Section titled “Roles y permisos”La autorizacion sigue teniendo tres capas:
| Capa | Donde se valida | Para que sirve |
|---|---|---|
| Rol | backend/frontend | Distinguir super_admin, admin tenant, doctor, assistant, recepcion, etc. |
| Permisos | session DTO + guards/UI + endpoints protegidos | Controlar funciones especificas. |
| Tenant operational | backend y session hydration | Evitar acceso a tenants no operativos. |
Frontend puede ocultar menus o redirigir, pero la seguridad real debe estar en backend. Cualquier endpoint sensible debe validar rol, permiso o tenant segun corresponda.
Archivos principales
Section titled “Archivos principales”| Area | Archivo | Responsabilidad |
|---|---|---|
| Login UI | hemia-assistance-front-legacy/src/views/Auth/Login.vue | Formulario, CSRF/bootstrap, login y ruta post-login. |
| Auth service frontend | hemia-assistance-front-legacy/src/services/core/auth/DAAuthService.js | Llamadas login, session, refresh, logout, forgot/reset. |
| CSRF service frontend | hemia-assistance-front-legacy/src/services/core/auth/DACsrfService.js | Obtiene y adjunta X-CSRF-Token. |
| Session service frontend | hemia-assistance-front-legacy/src/services/core/auth/DASessionService.js | Bootstrap de /api/auth/session. |
| Axios helper | hemia-assistance-front-legacy/src/services/config/axios.helper.js | withCredentials, CSRF y ausencia de Authorization en navegador. |
| Config frontend auth | hemia-assistance-front-legacy/src/services/config/authTransport.config.js | Flags de transporte por cookie. |
| Store auth | hemia-assistance-front-legacy/src/store/modules/auth/* | Estado runtime de sesion. |
| Router | hemia-assistance-front-legacy/src/router/index.js | Session hydration, guards y redireccion. |
| Cleanup frontend | hemia-assistance-front-legacy/src/utils/d2AuthCleanup.js | Limpieza runtime/storage legacy. |
| Cookie config backend | hemia-assistance-back-legacy/src/config/authCookie.config.js | Nombres y flags de cookies auth. |
| Security config | hemia-assistance-back-legacy/src/config/security.js | Startup validation, CSRF/CORS/security env. |
| Auth routes | hemia-assistance-back-legacy/src/routes/mysql/auth.js | Endpoints auth y middleware. |
| Auth controller | hemia-assistance-back-legacy/src/controller/mysql/auth.controller.js | Login, session, refresh, logout, forgot/reset. |
| Auth DAO | hemia-assistance-back-legacy/src/Dao/mysql/auth.dao.js | Validacion usuario/password, session, refresh, tenant. |
| Auth middleware | hemia-assistance-back-legacy/src/auth/authenticate.js | Lee access cookie y conserva soporte legacy. |
| CSRF middleware | hemia-assistance-back-legacy/src/middleware/csrf.middleware.js | Valida signed double-submit. |
| CORS middleware | hemia-assistance-back-legacy/src/middleware/corsCredentialed.middleware.js | Allowlist credentialed. |
Variables principales
Section titled “Variables principales”Backend production-like:
NODE_ENV=productionAUTH_COOKIE_MODE=d2BROWSER_AUTH_TRANSPORT=cookieREQUIRE_ACTIVE_SESSION=true
AUTH_COOKIE_ACCESS_NAME=__Host-medsync_atAUTH_COOKIE_REFRESH_NAME=__Host-medsync_rtAUTH_CSRF_COOKIE_NAME=__Host-medsync_csrfAUTH_COOKIE_SECURE=trueAUTH_COOKIE_SAMESITE=laxAUTH_COOKIE_DOMAIN=AUTH_COOKIE_PATH=/AUTH_COOKIE_MAX_AGE_MODE=session
CSRF_ENABLED=trueCORS_CREDENTIALS=trueCORS_ALLOWED_ORIGINS=https://app.<dominio>CSRF_ALLOWED_ORIGINS=https://app.<dominio>Frontend production-like:
VUE_APP_AUTH_COOKIE_MODE=d2VUE_APP_BROWSER_AUTH_TRANSPORT=cookieVUE_APP_CSRF_ENABLED=trueVUE_APP_CORE_URL_API=https://api.<dominio>/Nota: el valor literal d2 en AUTH_COOKIE_MODE y VUE_APP_AUTH_COOKIE_MODE es el nombre tecnico que usa el codigo para activar autenticacion por cookies. No representa una etapa de trabajo interna ni una instruccion de planificacion.
Local HTTP default:
# BackendAUTH_COOKIE_MODE=d2BROWSER_AUTH_TRANSPORT=cookieAUTH_COOKIE_ACCESS_NAME=medsync_at_devAUTH_COOKIE_REFRESH_NAME=medsync_rt_devAUTH_CSRF_COOKIE_NAME=medsync_csrf_devAUTH_COOKIE_SECURE=falseAUTH_COOKIE_SAMESITE=laxAUTH_COOKIE_DOMAIN=AUTH_COOKIE_PATH=/AUTH_COOKIE_MAX_AGE_MODE=sessionCSRF_ENABLED=trueCORS_CREDENTIALS=trueCORS_ALLOWED_ORIGINS=http://localhost:8080,http://127.0.0.1:8080,http://localhost:8081,http://127.0.0.1:8081,http://localhost:8082,http://127.0.0.1:8082,http://localhost:8098,http://127.0.0.1:8098CSRF_ALLOWED_ORIGINS=http://localhost:8080,http://127.0.0.1:8080,http://localhost:8081,http://127.0.0.1:8081,http://localhost:8082,http://127.0.0.1:8082,http://localhost:8098,http://127.0.0.1:8098
# FrontendVUE_APP_AUTH_COOKIE_MODE=d2VUE_APP_BROWSER_AUTH_TRANSPORT=cookieVUE_APP_CSRF_ENABLED=trueVUE_APP_CORE_URL_API=http://127.0.0.1:3009/Secretos reales:
- deben venir de secret manager o mecanismo infra equivalente;
- nunca deben commitearse;
- nunca deben copiarse a docs, tickets o chats.
Que debe revisar QA en browser
Section titled “Que debe revisar QA en browser”En la validacion production-like, revisar:
- Login tokenless.
- Access cookie
HttpOnly=true. - Refresh cookie
HttpOnly=true. - Cookies auth
Secure=true. SameSite=Lax.Path=/.__Host-*sinDomainsi aplica.document.cookiesin access/refresh.localStoragesin tokens.sessionStoragesin tokens.- Network sin
Authorization. - Mutaciones con
X-CSRF-Token. - Refresh con body
{}. - Logout limpia cookies.
- Reload y nueva pestana rehidratan
/api/auth/session. - Super Admin close produce
401. - Tenant restricted hard-block sin cookies.
- Usuario inactivo hard-block sin cookies.
- Flujo clinico minimo no usa tokens manuales.
Diagnostico rapido
Section titled “Diagnostico rapido”| Sintoma | Revisar primero |
|---|---|
| Login devuelve 403 | CSRF header/cookie, Origin, CSRF_ALLOWED_ORIGINS. |
| Login devuelve 401/404 generico | Credenciales, usuario activo, tenant hard-block, rate limit. |
| Login responde pero no hay sesion | Cookies bloqueadas, CORS credentials, withCredentials, domain/path. |
/api/auth/session no autentica | Access cookie ausente/expirada, CORS, cookie path/domain, sesion revocada. |
| Refresh falla | Refresh cookie, CSRF, body {}, reuse detection, sesion activa. |
| Logout no limpia | Atributos de cookie no coinciden entre set y clear. |
Aparece Authorization en Network | Frontend no esta compilado con transporte por cookies o cayo a legacy. |
| Token aparece en storage | Bug bloqueante; detener rollout. |
| Tenant restricted entra al sistema | Bug bloqueante; debe hard-block en login. |
| Reset password no cierra sesion vieja | No marcar production-ready; revisar invalidacion server-side. |
Lo que ya no debe documentarse como estado actual
Section titled “Lo que ya no debe documentarse como estado actual”Esta pagina reemplaza el modelo anterior. Ya no debe tratarse como destino final:
Authorizationcomo auth de navegador.- access token en
VueSession. - refresh token legible por JavaScript.
- “Mantener sesion iniciada”.
- router basado en JWT decodificado desde storage.
- permisos/tenant/business persistidos a largo plazo.
Puede existir compatibilidad legacy para desarrollo, pruebas o clientes no navegador, pero no es el contrato objetivo del navegador.
Resumen final
Section titled “Resumen final”El objetivo es que un XSS o un equipo clinico compartido no puedan robar tokens desde JavaScript o storage. Para lograrlo, el navegador autentica con cookies HttpOnly, el frontend usa CSRF para mutaciones y la identidad visible se hidrata desde /api/auth/session.
El sistema ya tiene base fuerte: sesiones server-side, refresh token v2, logout, Super Admin close, tenant hard-block y limpieza frontend. Lo que falta para production-ready no es escribir mas explicacion: falta validarlo en un ambiente production-like real con HTTPS, cookies Secure=true, dominios finales, SMTP/reset password, backups, observabilidad y rollback probado.