Saltearse al contenido

Autenticación y Autorización del API Gateway

Autenticación y Autorización del API Gateway

Descripción General

El API Gateway implementa un sistema de autenticación integral basado en JWT que valida las identidades de usuario y aplica control de acceso basado en roles (RBAC) en todos los Endpoints protegidos. Las operaciones de autenticación se delegan al microservicio MS_AUTH, que maneja la validación de credenciales, generación de tokens y gestión de usuarios.

Componentes Clave:

  • JWT Tokens: Autenticación sin estado usando JSON Web Tokens
  • JwtAuthGuard: Valida tokens y adjunta datos de usuario a las solicitudes
  • RolesGuard: Aplica control de acceso basado en roles
  • TokenValidationService: Se comunica con MS_AUTH para validación de token
  • OTP System: Flujos de verificación de email y restablecimiento de contraseña

Ciclo de Vida del Token:

  1. Generación: MS_AUTH genera JWT al iniciar sesión exitosamente
  2. Validación: Gateway valida el token en cada solicitud protegida
  3. Actualización: Actualmente no implementado (ver sección Brechas)
  4. Revocación: Manejado por MS_AUTH (invalidación de token al cerrar sesión)

Flujo de Autenticación

Flujo de Login

sequenceDiagram
    participant Client
    participant Gateway as API Gateway
    participant Controller as AuthController
    participant Handler as LoginCommandHandler
    participant MS_AUTH

    Client->>Gateway: POST /api/auth/login<br/>{ email, password }
    Gateway->>Controller: login()
    Controller->>Handler: Dispatch LoginCommand
    Handler->>MS_AUTH: Send 'auth.login' message<br/>{ email, password }
    MS_AUTH->>MS_AUTH: Validate credentials
    MS_AUTH->>MS_AUTH: Check email verification status
    MS_AUTH->>MS_AUTH: Generate JWT token
    MS_AUTH-->>Handler: { token, user: { id, email, name, role } }
    Handler-->>Controller: Return result
    Controller-->>Gateway: LoginResponseDto
    Gateway-->>Client: 200 OK<br/>{ token, user }

Ejemplo de Código: Solicitud de Login

Ventana de terminal
# Login request
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"password": "securePassword123"
}'
# Success response (wrapped by ResponseInterceptor)
{
"statusCode": 200,
"message": "Login successful",
"timestamp": "2025-01-15T10:30:00Z",
"path": "/api/auth/login",
"method": "POST",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjMiLCJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJyb2xlIjoiQ0xJRU5UIiwiaWF0IjoxNjQyMjQ0NDAwLCJleHAiOjE2NDIzMzA4MDB9.signature",
"user": {
"id": "user-123",
"email": "user@example.com",
"name": "John Doe",
"role": "CLIENT",
"isEmailVerified": true
}
},
"traceId": "550e8400-e29b-41d4-a716-446655440000"
}
# Note: The actual response structure depends on what the handler returns.
# If the handler returns an object with 'success' or 'statusCode', the interceptor passes it through.
# Use token in subsequent requests
curl http://localhost:3000/api/orders \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Archivo de Referencia: algesta-api-gateway-nestjs/src/infrastructure/controllers/auth.controller.ts


Proceso de Validación de Token

Flujo de Validación

sequenceDiagram
    participant Client
    participant Gateway as API Gateway
    participant Guard as JwtAuthGuard
    participant TokenService as TokenValidationService
    participant MS_AUTH
    participant Controller

    Client->>Gateway: GET /api/orders<br/>Authorization: Bearer <token>
    Gateway->>Guard: canActivate(context)
    Guard->>Guard: Extract token from header
    Guard->>TokenService: execute(token)
    TokenService->>MS_AUTH: Send 'auth.validateToken' message
    MS_AUTH->>MS_AUTH: Verify JWT signature
    MS_AUTH->>MS_AUTH: Check expiration
    MS_AUTH->>MS_AUTH: Check user status
    MS_AUTH->>MS_AUTH: Check email verification

    alt Token Valid
        MS_AUTH-->>TokenService: { valid: true, user: {...} }
        TokenService-->>Guard: User data
        Guard->>Gateway: Attach user to request.user
        Gateway->>Controller: Route to handler
        Controller-->>Client: 200 OK + data
    else Token Invalid/Expired
        MS_AUTH-->>TokenService: { valid: false, error: "..." }
        TokenService-->>Guard: Throw UnauthorizedException
        Guard-->>Client: 401 Unauthorized
    else Email Not Verified
        MS_AUTH-->>TokenService: { emailVerified: false }
        TokenService-->>Guard: Throw ForbiddenException
        Guard-->>Client: 403 Forbidden
    end

Implementación de JwtAuthGuard

El guard realiza los siguientes pasos:

  1. Extraer Token: Lee el header Authorization: Bearer <token>
  2. Validar Formato: Asegura que el token esté presente y correctamente formateado
  3. Llamar TokenValidationService: Delega la validación al servicio
  4. Verificar Respuesta: Valida el estado del token y datos de usuario
  5. Adjuntar Usuario: Agrega el objeto de usuario a request.user
  6. Permitir/Rechazar: Retorna true (permitir) o lanza excepción (rechazar)

Escenarios de Error:

EscenarioExceptionCódigo de EstadoMensaje
Token faltanteUnauthorizedException401”No token provided”
Token inválidoUnauthorizedException401”Invalid token”
Token expiradoUnauthorizedException401”Token expired”
Email no verificadoForbiddenException403”Email not verified”
Usuario inactivoUnauthorizedException401”User account inActivo”

Nota sobre Detección de Errores: El JwtAuthGuard usa coincidencia de cadenas en validationResult.reason de MS_AUTH para distinguir entre estos casos. Los mensajes de error exactos y las condiciones están acoplados a lo que MS_AUTH retorna (ej. verificando si reason incluye “not verified”, “expired”, o “not found”). Esta dependencia semántica significa que cambios a los mensajes de error de MS_AUTH pueden requerir actualizaciones correspondientes en la lógica del guard.

Ejemplo de Código: Usando Guards

@Controller("orders")
@ApiTags("Orders")
export class OrdersManagementController {
@Get()
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
@ApiOperation({ summary: "Get all orders" })
async getAllOrders(@CurrentUser() user: AuthenticatedUser) {
// user object is available here
// Contains: userId, email, name, role, isEmailVerified, etc.
return this.commandBus.execute(new GetAllOrdersQuery(user));
}
}

Archivos de Referencia:

  • algesta-api-gateway-nestjs/src/shared/guards/jwt-auth.guard.ts
  • algesta-api-gateway-nestjs/src/shared/services/token-validation.service.ts

Control de Acceso Basado en Roles (RBAC)

Roles Soportados

El sistema soporta cinco roles de usuario, cada uno con permisos específicos:

RolDescripciónNivel de AccesoUsuarios Típicos
ADMINAcceso completo al sistemaTodos los Endpoints y operacionesAdministradores del sistema
AGENTGestión y curación de órdenesCRUD de órdenes, asignación de proveedores, publicación subastasAgentes de servicio al cliente
PROVIDEREntrega de serviciosParticipación en subastas, reportes de trabajo, cumplimientoProveedores de servicio/contratistas
CLIENTSolicitudes de servicioCreación de órdenes, aprobaciones, calificación de proveedoresClientes finales
EMPLOYEEOperaciones de la empresaFuncionalidades específicas de empresa, gestión de activosEmpleados de la empresa

Implementación de RolesGuard

El RolesGuard aplica control de acceso basado en roles mediante:

  1. Lectura de Roles Requeridos: Extrae roles de los metadatos del decorador @Roles
  2. Extracción de Rol de Usuario: Obtiene user.role del token JWT (adjuntado por JwtAuthGuard)
  3. Comparación de Roles: Verifica si el usuario tiene alguno de los roles requeridos
  4. Permitir/Rechazar: Retorna true si está autorizado, lanza ForbiddenException de lo contrario

Flujo del Guard:

sequenceDiagram
    participant Request
    participant JwtAuthGuard
    participant RolesGuard
    participant Controller

    Request->>JwtAuthGuard: canActivate()
    JwtAuthGuard->>Request: Attach user to request
    Request->>RolesGuard: canActivate()
    RolesGuard->>RolesGuard: Get @Roles metadata
    RolesGuard->>RolesGuard: Check user.role in required roles

    alt Role Authorized
        RolesGuard->>Controller: Allow access
    else Role Not Authorized
        RolesGuard-->>Request: 403 Forbidden
    end

Ejemplos de Endpoints con Restricciones de Rol

EndpointMétodoRoles RequeridosPropósito
/api/ordersPATCHADMIN, AGENTActualizar detalles de orden
/api/orders/publish/:orderIdPOSTADMIN, AGENTPublicar orden al mercado
/api/provider/auctionsGETPROVIDERListar subastas disponibles
/api/provider/auction-responsePOSTPROVIDEREnviar oferta de subasta
/api/client/submit-approvalPOSTCLIENTAprobar cotización/trabajo
/api/auction/publishPOSTADMIN, AGENTCrear nueva subasta
/api/technician/assign-orderPOSTADMIN, AGENTAsignar orden a técnico

Ejemplo de Código: Guards Combinados

@Controller("orders")
export class OrdersManagementController {
@Patch(":orderId")
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN, UserRole.AGENT)
@ApiBearerAuth()
@ApiOperation({ summary: "Update order (ADMIN/AGENT only)" })
async updateOrder(
@CurrentUser() user: AuthenticatedUser,
@Param("orderId") orderId: string,
@Body() updateDto: UpdateOrderDto
) {
// Only ADMIN or AGENT can reach this point
return this.commandBus.execute(new UpdateOrderCommand(orderId, updateDto));
}
}

Probando Acceso Basado en Roles:

Ventana de terminal
# CLIENT trying to access ADMIN-only endpoint (should fail)
curl -X PATCH http://localhost:3000/api/orders/order-123 \
-H "Authorization: Bearer <client-token>" \
-H "Content-Type: application/json" \
-d '{"status":"PUBLISHED"}'
# Response (BaseResponseDto structure from HttpExceptionFilter):
{
"success": false,
"message": "Forbidden resource",
"data": null,
"error": {
"statusCode": 403,
"error": "Forbidden",
"message": "Forbidden resource",
"timestamp": "2025-01-15T10:30:00Z",
"path": "/api/orders/order-123",
"method": "PATCH",
"traceId": "uuid-here"
}
}
# ADMIN trying same endpoint (should succeed)
curl -X PATCH http://localhost:3000/api/orders/order-123 \
-H "Authorization: Bearer <admin-token>" \
-H "Content-Type: application/json" \
-d '{"status":"PUBLISHED"}'
# Response:
{
"statusCode": 200,
"message": "Order updated successfully",
"data": { ... },
"traceId": "uuid-here"
}

Archivos de Referencia:

  • algesta-api-gateway-nestjs/src/shared/guards/roles.guard.ts
  • algesta-api-gateway-nestjs/src/shared/decorators/roles.decorator.ts
  • algesta-api-gateway-nestjs/src/shared/enums/user-role.enum.ts

Flujos de Registro de Usuario

Flujo de Registro Web

sequenceDiagram
    participant Client
    participant Gateway
    participant MS_AUTH
    participant EmailService

    Client->>Gateway: POST /api/auth/register-web<br/>{ email, name, password, ... }
    Gateway->>MS_AUTH: Send 'auth.register' message
    MS_AUTH->>MS_AUTH: Validate email uniqueness
    MS_AUTH->>MS_AUTH: Hash password
    MS_AUTH->>MS_AUTH: Create user (isEmailVerified: false)
    MS_AUTH->>EmailService: Send verification email
    MS_AUTH-->>Gateway: { userId, email, message }
    Gateway-->>Client: 201 Created<br/>"Check email for verification"

    Note over Client,EmailService: User receives email with verification link

    Client->>Gateway: POST /api/auth/otp/validate/:email/:code
    Gateway->>MS_AUTH: Validate OTP code
    MS_AUTH->>MS_AUTH: Update isEmailVerified: true
    MS_AUTH-->>Gateway: { verified: true }
    Gateway-->>Client: 200 OK<br/>"Email verified successfully"

Endpoint: POST /api/auth/register-web

Cuerpo de la Solicitud:

{
"email": "newuser@example.com",
"name": "Jane Smith",
"identification": "1234567890",
"phone": "+573001234567",
"password": "SecurePass123!",
"isEmployee": false
}

Respuesta:

{
"statusCode": 201,
"message": "User registered successfully. Please verify your email.",
"data": {
"userId": "user-456",
"email": "newuser@example.com"
},
"traceId": "uuid-here"
}

Archivo de Referencia: algesta-api-gateway-nestjs/src/infrastructure/controllers/auth.controller.ts

Flujo de Verificación OTP

1. Enviar OTP:

Ventana de terminal
POST /api/auth/otp/send/:email
# Example
curl -X POST http://localhost:3000/api/auth/otp/send/user@example.com
# Response
{
"statusCode": 200,
"message": "OTP sent to email",
"data": {
"email": "user@example.com",
"expiresIn": "10 minutes"
}
}

2. Validar OTP:

Ventana de terminal
POST /api/auth/otp/validate/:email/:code
# Example
curl -X POST http://localhost:3000/api/auth/otp/validate/user@example.com/123456
# Success Response
{
"statusCode": 200,
"message": "Email verified successfully",
"data": {
"verified": true
}
}
# Error Response (invalid code) - BaseResponseDto structure
{
"success": false,
"message": "Invalid or expired OTP code",
"data": null,
"error": {
"statusCode": 400,
"error": "Bad Request",
"message": "Invalid or expired OTP code",
"timestamp": "2025-01-15T10:30:00Z",
"path": "/api/auth/otp/validate/user@example.com/123456",
"method": "POST",
"traceId": "uuid-here"
}
}

Características del OTP:

  • Formato: Código numérico de 6 dígitos
  • Validez: Tiempo limitado (configurable en MS_AUTH, típicamente 10 minutos)
  • Uso único: Código invalidado después de validación exitosa
  • Entrega: Email vía servicio de notificación de MS_AUTH

Archivo de Referencia: algesta-api-gateway-nestjs/src/infrastructure/controllers/auth.otp.controller.ts

Flujo de Registro de Empleado

Los empleados se registran con asociación de empresa:

Ventana de terminal
POST /api/employee/register
# Request
{
"email": "employee@company.com",
"name": "Employee Name",
"identification": "9876543210",
"phone": "+573009876543",
"password": "EmpPass123!",
"companyNit": "900123456-7"
}
# Process:
# 1. Validate company NIT exists
# 2. Create user account with EMPLOYEE role
# 3. Associate employee with company
# 4. Send verification email
# Response
{
"statusCode": 201,
"message": "Employee registered successfully",
"data": {
"userId": "emp-789",
"companyId": "company-123",
"email": "employee@company.com"
}
}

Archivo de Referencia: algesta-api-gateway-nestjs/src/infrastructure/controllers/employee.controller.ts


Flujo de Restablecimiento de Contraseña

Proceso Completo de Restablecimiento de Contraseña

sequenceDiagram
    participant User
    participant Gateway
    participant MS_AUTH
    participant EmailService

    User->>Gateway: POST /api/auth/forgot-password/:email
    Gateway->>MS_AUTH: Request password reset
    MS_AUTH->>MS_AUTH: Generate OTP (6 digits)
    MS_AUTH->>EmailService: Send OTP email
    MS_AUTH-->>Gateway: { message: "OTP sent" }
    Gateway-->>User: 200 OK

    Note over User,EmailService: User receives OTP email

    User->>Gateway: POST /api/auth/validate-forgot-password-otp/:email/:code
    Gateway->>MS_AUTH: Validate OTP
    MS_AUTH->>MS_AUTH: Verify OTP is valid and not expired
    MS_AUTH->>MS_AUTH: Generate reset token (2-hour validity)
    MS_AUTH-->>Gateway: { resetToken: "token-abc123" }
    Gateway-->>User: 200 OK + resetToken

    User->>Gateway: POST /api/auth/reset-password<br/>{ resetToken, newPassword }
    Gateway->>MS_AUTH: Reset password
    MS_AUTH->>MS_AUTH: Validate reset token
    MS_AUTH->>MS_AUTH: Hash new password
    MS_AUTH->>MS_AUTH: Update user password
    MS_AUTH->>MS_AUTH: Invalidate reset token
    MS_AUTH-->>Gateway: { message: "Password reset successful" }
    Gateway-->>User: 200 OK

Implementación Paso a Paso

Paso 1: Solicitar Restablecimiento de Contraseña

Ventana de terminal
POST /api/auth/forgot-password/:email
# Example
curl -X POST http://localhost:3000/api/auth/forgot-password/user@example.com
# Response
{
"statusCode": 200,
"message": "Password reset OTP sent to email",
"data": {
"email": "user@example.com"
}
}

Paso 2: Validar OTP y Obtener Token de Restablecimiento

Ventana de terminal
POST /api/auth/validate-forgot-password-otp/:email/:code
# Example
curl -X POST http://localhost:3000/api/auth/validate-forgot-password-otp/user@example.com/123456
# Success Response
{
"statusCode": 200,
"message": "OTP validated successfully",
"data": {
"resetToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6InVzZXJAZXhhbXBsZS5jb20iLCJwdXJwb3NlIjoicGFzc3dvcmRSZXNldCIsImlhdCI6MTY0MjI0NDQwMCwiZXhwIjoxNjQyMjUxNjAwfQ.signature",
"expiresIn": "2 hours"
}
}

Paso 3: Restablecer Contraseña con Token

Ventana de terminal
POST /api/auth/reset-password
# Request
curl -X POST http://localhost:3000/api/auth/reset-password \
-H "Content-Type: application/json" \
-d '{
"resetToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"newPassword": "NewSecurePass123!"
}'
# Success Response
{
"statusCode": 200,
"message": "Password reset successfully",
"data": {
"email": "user@example.com"
}
}
# Error Response (invalid/expired token) - BaseResponseDto structure
{
"success": false,
"message": "Invalid or expired reset token",
"data": null,
"error": {
"statusCode": 400,
"error": "Bad Request",
"message": "Invalid or expired reset token",
"timestamp": "2025-01-15T10:30:00Z",
"path": "/api/auth/reset-password",
"method": "POST",
"traceId": "uuid-here"
}
}

Consideraciones de Seguridad

Seguridad del OTP:

  • Validez limitada en tiempo (típicamente 10 minutos)
  • Uso único (invalidado después de validación exitosa)
  • Limitación de tasa en generación de OTP (actualmente no implementado - ver Brechas)

Seguridad del Token de Restablecimiento:

  • Ventana de expiración corta (2 horas)
  • Uso único (invalidado inmediatamente después del restablecimiento de contraseña)
  • No puede ser reutilizado incluso si no ha expirado
  • Contiene email y propósito en el payload del JWT

Validación de Contraseña:

  • Aplicada por MS_AUTH durante el restablecimiento de contraseña
  • Longitud mínima, requisitos de complejidad
  • Verificación de contraseña anterior (actualmente no implementado - ver Brechas)

Archivo de Referencia: algesta-api-gateway-nestjs/src/infrastructure/controllers/auth.controller.ts


Extracción de Usuario Actual

Decorador @CurrentUser()

El decorador @CurrentUser() proporciona acceso seguro a los datos del usuario autenticado dentro de los métodos del controlador.

Ejemplo de Uso:

@Controller("orders")
export class OrdersController {
@Get()
@UseGuards(JwtAuthGuard)
async getMyOrders(@CurrentUser() user: AuthenticatedUser) {
// user object is automatically extracted from request.user
console.log(user.userId); // "user-123"
console.log(user.email); // "user@example.com"
console.log(user.role); // UserRole.CLIENT
console.log(user.name); // "John Doe"
return this.orderService.findByUserId(user.userId);
}
@Post()
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(UserRole.ADMIN, UserRole.AGENT)
async createOrder(
@CurrentUser() user: AuthenticatedUser,
@Body() createDto: CreateOrderDto
) {
// Only ADMIN or AGENT can reach this point
return this.orderService.create(createDto, user.userId);
}
}

Interfaz AuthenticatedUser

El objeto de usuario adjuntado a las solicitudes contiene:

interface AuthenticatedUser {
userId: string; // Unique user identifier (guaranteed)
email: string; // User email address (guaranteed)
name?: string; // Full name (optional - depends on MS_AUTH response)
identification?: string; // Government ID number (optional - depends on MS_AUTH response)
role?: UserRole; // User role (optional - depends on MS_AUTH response)
isEmailVerified?: boolean; // Email verification status (optional - depends on MS_AUTH response)
mfaEnabled?: boolean; // Multi-factor authentication status (optional - depends on MS_AUTH response)
token?: string; // Original JWT token (optional - for logout operations)
phone?: string; // Phone number (optional)
companyId?: string; // Company ID (optional - for employees)
}

Detalles de los Campos:

CampoTipoGarantizadoDescripción
userIdstringIdentificador único de MS_AUTH; presente confiablemente según lógica actual del guard
emailstringDirección de email del usuario; presente confiablemente según lógica actual del guard
namestringNo*Nombre completo; presencia depende de la respuesta de MS_AUTH
identificationstringNo*Número de ID gubernamental o pasaporte; presencia depende de la respuesta de MS_AUTH
roleUserRoleNo*Uno de: ADMIN, AGENT, PROVIDER, CLIENT, EMPLOYEE; presencia depende de la respuesta de MS_AUTH
isEmailVerifiedbooleanNo*Si el email está verificado; presencia depende de la respuesta de MS_AUTH
mfaEnabledbooleanNo*Si MFA está habilitado (aún no aplicado); presencia depende de la respuesta de MS_AUTH
tokenstringNo*JWT original para operaciones de logout; presencia depende de la respuesta de MS_AUTH
phonestringNoNúmero de teléfono con código de país; campo opcional
companyIdstringNoAsociación con empresa (para rol EMPLOYEE); campo opcional

Nota Importante: Solo userId y email están garantizados de estar presentes por la implementación actual de JwtAuthGuard. Todos los demás campos dependen de lo que MS_AUTH retorna durante la validación del token. El código que depende de campos como name, identification, mfaEnabled, o companyId debe manejar su potencial ausencia con gracia (ej. user.name || 'Unknown').

Archivos de Referencia:

  • algesta-api-gateway-nestjs/src/shared/decorators/current-user.decorator.ts
  • algesta-api-gateway-nestjs/src/shared/guards/jwt-auth.guard.ts

Mejores Prácticas de Seguridad

Almacenamiento de Token (Lado del Cliente)

Enfoques Recomendados:

  1. HttpOnly Cookies (Más Seguro)

    • Almacenado en cookie httpOnly, inaccesible a JavaScript
    • Protege contra ataques XSS
    • Requiere protección CSRF
    • Actualmente no implementado - API usa tokens Bearer
  2. localStorage (Implementación Actual)

    • Fácil de implementar
    • Vulnerable a ataques XSS
    • Requiere prevención cuidadosa de XSS
    • Usado por la mayoría de clientes SPA
  3. sessionStorage (Alternativa)

    • Se limpia cuando se cierra la pestaña
    • Más seguro que localStorage para datos sensibles
    • Mismas vulnerabilidades XSS

Transmisión de Token

Siempre usar HTTPS en producción:

// Development (HTTP allowed)
const apiUrl = "http://localhost:3000/api";
// Production (HTTPS required)
const apiUrl = "https://api.algesta.com/api";

Siempre incluir el token en el header Authorization:

// Correct
fetch("/api/orders", {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
});
// Incorrect (never send token in URL or body)
fetch(`/api/orders?token=${token}`); // NEVER DO THIS

Aplicación de Verificación de Email

Algunos Endpoints requieren email verificado:

@Post('sensitive-action')
@UseGuards(JwtAuthGuard, EmailVerifiedGuard)
async sensitiveAction(@CurrentUser() user: AuthenticatedUser) {
// Solo usuarios con isEmailVerified: true pueden acceder
}

Brecha Actual: La verificación de email no se aplica consistentemente en todos los Endpoints sensibles.

Estrategia de Expiración de Token

Implementación Actual:

  • Los tokens JWT tienen tiempo de expiración (configurado en MS_AUTH)
  • Sin mecanismo automático de actualización
  • Los usuarios deben volver a iniciar sesión cuando el token expira

Estrategia Recomendada (ver sección Brechas):

  • Access tokens de corta duración (15-30 minutos)
  • Refresh tokens de larga duración (7-30 días)
  • Actualización automática de token antes de la expiración
  • Ventana de sesión deslizante

Limitación de Tasa en Endpoints de Autenticación

Brecha Actual: Sin limitación de tasa implementada.

Límites Recomendados:

  • Login: 5 intentos por 15 minutos por IP/email
  • Restablecimiento de contraseña: 3 intentos por hora por email
  • Generación OTP: 3 intentos por hora por email
  • Registro: 10 intentos por hora por IP

Referencia de Endpoints de Autenticación

Lista Completa de Endpoints

MétodoRutaDescripciónAuth RequeridoRolesCuerpo de SolicitudRespuesta
POST/api/auth/loginAutenticar usuarioNo-{ email, password }{ token, user }
POST/api/auth/register-webRegistrar usuario webNo-{ email, name, identification, phone, password, isEmployee }{ userId, email, message }
POST/api/auth/forgot-password/:emailSolicitar restablecimiento de contraseñaNo--{ message }
POST/api/auth/validate-forgot-password-otp/:email/:codeValidar OTP de restablecimientoNo--{ resetToken }
POST/api/auth/reset-passwordRestablecer contraseña con tokenNo-{ resetToken, newPassword }{ message }
POST/api/auth/otp/send/:emailEnviar OTP de verificaciónNo--{ message }
POST/api/auth/otp/validate/:email/:codeValidar OTP de verificaciónNo--{ verified: true }

Documentación Detallada de Endpoints

POST /api/auth/login

  • Propósito: Autenticar usuario con email y contraseña
  • Estado de Éxito: 200
  • Estado de Error: 401 (Credenciales inválidas), 400 (Error de validación)
  • Ejemplo: Ver sección Flujo de Login

POST /api/auth/register-web

  • Propósito: Registrar nuevo usuario web
  • Estado de Éxito: 201
  • Estado de Error: 409 (Usuario existe), 400 (Error de validación)
  • Ejemplo: Ver sección Flujo de Registro Web

POST /api/auth/forgot-password/:email

POST /api/auth/validate-forgot-password-otp/:email/:code

POST /api/auth/reset-password

  • Propósito: Restablecer contraseña usando token de restablecimiento
  • Estado de Éxito: 200
  • Estado de Error: 400 (Token inválido, contraseña débil)
  • Ejemplo: Ver sección Flujo de Restablecimiento de Contraseña

Probando Autenticación

Flujo de Prueba Completo

1. Registrar Nuevo Usuario

Ventana de terminal
curl -X POST http://localhost:3000/api/auth/register-web \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"name": "Test User",
"identification": "1234567890",
"phone": "+573001234567",
"password": "TestPass123!",
"isEmployee": false
}'

2. Verificar Email con OTP

Ventana de terminal
# Send OTP
curl -X POST http://localhost:3000/api/auth/otp/send/test@example.com
# Validate OTP (check email for code)
curl -X POST http://localhost:3000/api/auth/otp/validate/test@example.com/123456

3. Iniciar Sesión

Ventana de terminal
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{
"email": "test@example.com",
"password": "TestPass123!"
}'
# Save the token from response
export TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

4. Access Protected Endpoint

Ventana de terminal
curl http://localhost:3000/api/orders \
-H "Authorization: Bearer $TOKEN"

5. Test Role-Based Access (Should Fail)

Ventana de terminal
# CLIENT trying to access ADMIN endpoint
curl -X PATCH http://localhost:3000/api/orders/order-123 \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"status":"PUBLISHED"}'
# Expected: 403 Forbidden

6. Test Password Reset

Ventana de terminal
# Request reset
curl -X POST http://localhost:3000/api/auth/forgot-password/test@example.com
# Validate OTP (check email)
curl -X POST http://localhost:3000/api/auth/validate-forgot-password-otp/test@example.com/123456
# Reset password with token
curl -X POST http://localhost:3000/api/auth/reset-password \
-H "Content-Type: application/json" \
-d '{
"resetToken": "token-from-previous-step",
"newPassword": "NewPass123!"
}'

Error Response Examples

Invalid Credentials:

{
"statusCode": 401,
"message": "Invalid email or password",
"error": "Unauthorized",
"timestamp": "2025-01-15T10:30:00Z",
"path": "/api/auth/login",
"traceId": "uuid-here"
}

Token Expired:

{
"statusCode": 401,
"message": "Token has expired",
"error": "Unauthorized",
"timestamp": "2025-01-15T10:30:00Z",
"path": "/api/orders",
"traceId": "uuid-here"
}

Insufficient Permissions:

{
"statusCode": 403,
"message": "Forbidden resource",
"error": "Forbidden",
"timestamp": "2025-01-15T10:30:00Z",
"path": "/api/orders/order-123",
"traceId": "uuid-here"
}

Integration with Microservicios

Gateway → MS_AUTH Communication

All authentication Operaciones are delegated to the MS_AUTH Microservicio:

Message Patterns:

  • auth.login - User login
  • auth.register - User registration
  • auth.validateToken - Token validation
  • auth.forgotPassword - Password reset request
  • auth.validateOtp - OTP validation
  • auth.resetPassword - Password reset execution

Example Communication:

// In TokenValidationService
async execute(token: string): Promise<AuthenticatedUser> {
try {
const result = await this.msAuthClient
.send('auth.validateToken', { token })
.toPromise();
if (!result.valid) {
throw new UnauthorizedException('Invalid token');
}
if (!result.user.isEmailVerified) {
throw new ForbiddenException('Email not verified');
}
return result.user;
} catch (error) {
// Handle MS_AUTH errors
throw new UnauthorizedException('Token validation failed');
}
}

Error Handling and Propagation

MS_AUTH Error → Gateway Error Mapping:

MS_AUTH ErrorGateway ExceptionEstado Code
User not foundUnauthorizedException401
Invalid passwordUnauthorizedException401
Token expiredUnauthorizedException401
Email not verifiedForbiddenException403
User inActivoUnauthorizedException401
Validation errorBadRequestException400
Service unavailableServiceUnavailableException503

Circuit Breaker Integration:

  • Authentication calls wrapped in circuit breaker
  • Fast-fail when MS_AUTH is unavailable
  • Fallback: Return cached user data for token validation (not currently implemented)

For more details on resilience patterns, see API Gateway Resilience.

For inter-service communication details, see Inter-Service Communication.


Gaps and Recommendations

Current Gaps

  1. Refresh Token Mechanism

    • Estado: Not implemented
    • Impact: Users must re-login when JWT expires
    • Recommendation: Implement refresh token flow with sliding sessions
    • Implementación:
      POST /api/auth/refresh
      Body: { refreshToken: "..." }
      Response: { accessToken: "...", refreshToken: "..." }
  2. Multi-Factor Authentication (MFA)

    • Estado: Partially implemented (mfaEnabled flag exists but not enforced)
    • Impact: Reduced security for sensitive Operaciones
    • Recommendation: Completo MFA Implementación with TOTP/SMS
    • Prioridad: High for ADMIN and AGENT roles
  3. Session Management

    • Estado: Not implemented
    • Impact: No centralized session tracking or revocation
    • Recommendation: Implement session store (Redis) for token tracking
    • Benefits: Enable logout from all devices, track Activo sessions
  4. Rate Limiting on Auth Endpoints

    • Estado: Not implemented
    • Impact: Vulnerable to brute force attacks
    • Recommendation: Implement @nestjs/throttler
    • Suggested Limits:
      • Login: 5 attempts per 15 minutes
      • Password reset: 3 attempts per hour
      • OTP generation: 3 attempts per hour
  5. Password History

    • Estado: Not implemented
    • Impact: Users can reuse old passwords
    • Recommendation: Store hash of last 5 passwords, prevent reuse
  6. Account Lockout

    • Estado: Not implemented
    • Impact: Unlimited login attempts possible
    • Recommendation: Lock account after N failed attempts, require admin unlock or time-based unlock
  7. Audit Logging for Auth Events

    • Estado: Basic logging exists, no structured audit trail
    • Impact: Difficult to track suspicious authentication activity
    • Recommendation: Implement dedicated auth audit log with:
      • Login attempts (success/failure)
      • Password changes
      • Role changes
      • Token generation/validation
  8. OAuth2/OIDC Support

    • Estado: Not implemented
    • Impact: Cannot integrate with third-party identity providers
    • Recommendation: Add OAuth2 support for Google, Microsoft, etc.
    • Use Case: Enterprise SSO integration
  9. Email Verification Enforcement

    • Estado: Inconsistent across Endpoints
    • Impact: Unverified users can access some protected resources
    • Recommendation: Create EmailVerifiedGuard and apply consistently
  10. Token Blacklisting

    • Estado: Not implemented
    • Impact: Tokens remain valid until expiration even after logout
    • Recommendation: Implement Redis-based token blacklist
    • Use Case: Immediate logout, security incidents

Security Recommendations

High Prioridad:

  1. Implement rate limiting on all auth Endpoints
  2. Add refresh token mechanism
  3. Enforce email verification consistently
  4. Implement account lockout after failed attempts
  5. Add comprehensive audit logging

Medium Prioridad: 6. Completo MFA Implementación for admin roles 7. Implement session management with Redis 8. Add password history tracking 9. Implement token blacklisting for logout

Low Prioridad: 10. Add OAuth2/OIDC support for third-party auth 11. Implement device tracking and management 12. Add IP-based geolocation and suspicious login detection

Pruebas Recommendations

Automated Pruebas Needed:

  • Unit Pruebas for guards and validation service
  • Integration Pruebas for Completo auth flows
  • Security Pruebas for:
    • Token tampering
    • Expired token handling
    • Role elevation attempts
    • Brute force attack simulation

Manual Security Pruebas:

  • Penetration Pruebas of auth Endpoints
  • Token lifetime and expiration verification
  • Role-based access control verification
  • Password reset flow security

Referencias Cruzadas

Referenced Files

  • algesta-api-gateway-nestjs/src/shared/guards/jwt-auth.guard.ts
  • algesta-api-gateway-nestjs/src/shared/guards/roles.guard.ts
  • algesta-api-gateway-nestjs/src/shared/services/token-validation.service.ts
  • algesta-api-gateway-nestjs/src/shared/decorators/roles.decorator.ts
  • algesta-api-gateway-nestjs/src/shared/decorators/current-user.decorator.ts
  • algesta-api-gateway-nestjs/src/infrastructure/controllers/auth.controller.ts
  • algesta-api-gateway-nestjs/src/infrastructure/controllers/auth.otp.controller.ts

Resumen

The API Gateway implements a robust JWT-based authentication system with role-based access control, delegating all auth Operaciones to the MS_AUTH Microservicio. The system provides comprehensive user registration, email verification, and password reset flows.

Key Strengths:

  • Clean separation of concerns with guards and services
  • Type-safe user extraction with @CurrentUser() decorator
  • Flexible role-based access control
  • Comprehensive OTP-based verification flows
  • Integration with MS_AUTH for centralized user management

Prioridad Improvements:

  1. Implement refresh token mechanism
  2. Add rate limiting to prevent brute force attacks
  3. Completo MFA Implementación for sensitive roles
  4. Implement session management and token blacklisting
  5. Enforce email verification consistently

For Completo API Endpoint Documentoation, see API Gateway API Reference.