Autenticación
La API usa JWT Bearer con refresh token y validación de tenant. Hay dos formas de autenticarse según la configuración del tenant:
- Credenciales (
POST /auth/login) — usuario y contraseña de TwiinsHRM. - SSO con Keycloak (
POST /auth/keycloak) — cuando el tenant tieneLogin_SSO = 'S'en sus parámetros.
La regla de oro: tenant en header y en token
Sección titulada «La regla de oro: tenant en header y en token»Toda petición autenticada lleva dos credenciales, y deben ser coherentes entre sí:
Authorization: Bearer <jwt>x-tenant-id: <alias-del-tenant>El JWT incluye un claim tenant. El backend valida que x-tenant-id coincida exactamente con ese claim; si no coinciden responde 401 Unauthorized: Invalid tenant ID. No se puede usar el token de un tenant contra otro.
Flujo de autenticación
Sección titulada «Flujo de autenticación»sequenceDiagram
participant App as App móvil
participant API as th-core-api
App->>API: POST /auth/login (x-tenant-id + username/password)
API-->>App: { token: { bearer, refresh } }
App->>App: Guardar tokens en Keychain/Keystore
App->>API: GET /auth/session (Bearer + x-tenant-id)
API-->>App: Perfil del usuario autenticado
Note over App,API: ...uso normal de la API...
App->>API: GET /me/... (Bearer expirado)
API-->>App: 401 Unauthorized
App->>API: POST /auth/refresh { token: { refresh } }
API-->>App: { token: { bearer, refresh } } (nuevos)
App->>API: Reintentar la petición original
App->>API: POST /auth/refresh (refresh inválido/expirado)
API-->>App: 401 → volver a la pantalla de login
Endpoints
Sección titulada «Endpoints»POST /auth/login
Sección titulada «POST /auth/login»Autentica con usuario y contraseña. Requiere x-tenant-id (aún sin Authorization).
Request
{ "username": "jperez", "password": "********"}Response 200
{ "token": { "bearer": "eyJhbGciOiJIUzI1NiIs...", "refresh": "eyJhbGciOiJIUzI1NiIs..." }}| Campo | Descripción |
|---|---|
token.bearer |
JWT de acceso, de corta duración. Va en Authorization: Bearer en cada petición. |
token.refresh |
JWT de renovación, expira a los 7 días. Solo se usa contra /auth/refresh. |
Errores
| Código | Causa |
|---|---|
401 |
Credenciales inválidas (Invalid credentials). |
403 |
Usuario deshabilitado (User is disabled). |
404 |
x-tenant-id ausente o tenant inexistente. |
POST /auth/refresh
Sección titulada «POST /auth/refresh»Renueva ambos tokens a partir del refresh token. No requiere Authorization.
Request
{ "token": { "refresh": "eyJhbGciOiJIUzI1NiIs..." }}Response 200 — misma forma que el login: { token: { bearer, refresh } }. Guarda ambos tokens nuevos (el refresh también rota).
Errores: 401 si el refresh token es inválido o expiró → la app debe cerrar sesión y volver al login.
GET /auth/session
Sección titulada «GET /auth/session»Devuelve el perfil del usuario autenticado. Úsalo tras el login para poblar la pantalla de inicio/perfil.
Response 200 (campos principales)
| Campo | Tipo | Descripción |
|---|---|---|
username |
string | Usuario autenticado. |
identification |
string | Cédula / documento. |
name, lastName, fullName |
string | Nombres. |
mail |
string | Correo. |
avatar |
string | URL de la foto de perfil. |
gender |
enum | Género (UserGenderEnum). |
positionId |
string | Cargo. |
supervisor … supervisor4 |
string | Cadena de supervisión. |
cellPhone |
string | Celular. |
status |
string | Estado del usuario. |
Incluye además objetos anidados de departamento, oficina y posición.
POST /auth/logout
Sección titulada «POST /auth/logout»Invalida las cachés de sesión y permisos del usuario en el servidor. Responde { "message": "Session closed successfully" }.
Después de llamarlo, la app debe borrar los tokens locales. Si el tenant usa SSO, además hay que cerrar la sesión en Keycloak (ver abajo).
POST /auth/password
Sección titulada «POST /auth/password»Cambio de contraseña del usuario autenticado (requiere Bearer). El detalle del contrato se documenta junto al módulo Mis Datos.
SSO con Keycloak (tenants con Login_SSO = 'S')
Sección titulada «SSO con Keycloak (tenants con Login_SSO = 'S')»Antes de mostrar la pantalla de login, consulta los parámetros del tenant (ver Tenants y ambientes). Si Login_SSO es 'S', el login es contra Keycloak con Authorization Code + PKCE:
- La app abre el navegador (Custom Tab / ASWebAuthenticationSession) hacia el servidor Keycloak:
https://auth.twiinshrm.com/realms/{alias-del-tenant}/protocol/openid-connect/authcon elclient_iddel portal,redirect_uripropio de la app y elcode_challengePKCE. - El usuario se autentica y Keycloak redirige al
redirect_uricon uncode. - La app intercambia el código contra la API (no contra Keycloak directamente):
POST /auth/keycloakx-tenant-id: <alias>
{ "code": "<authorization_code>", "codeVerifier": "<pkce_code_verifier>", "redirectUri": "<el mismo redirect_uri usado en el paso 1>"}La respuesta entrega los mismos tokens { token: { bearer, refresh } } que el login normal — de ahí en adelante todo el flujo es idéntico.
Logout con SSO: además de POST /auth/logout y de borrar los tokens locales, abre la URL de logout de Keycloak (.../realms/{alias}/protocol/openid-connect/logout) para cerrar la sesión del navegador.
Ciclo de vida del token en la app
Sección titulada «Ciclo de vida del token en la app»- Guarda los tokens en almacenamiento seguro (iOS Keychain / Android Keystore). Nunca en preferencias planas.
- Ante un
401en cualquier endpoint: intenta una vezPOST /auth/refresh; si funciona, reintenta la petición original con el nuevo bearer. - Si el refresh también devuelve
401: borra tokens y navega al login. - Serializa el refresh: si varias peticiones reciben
401a la vez, ejecuta un solo refresh y reintenta las demás con el token nuevo (el refresh rota en cada uso).