← Volver al listado de tecnologías

Capítulo 5: Autenticación, Autorización y Seguridad Avanzada

Por: Tu Nombre
cloudflareworkershonosecurityauthenticationauthorizationjwtcloudflare-accessrbacowaspmiddlewaretesting

Capítulo 5: Autenticación, Autorización y Seguridad Avanzada

Con nuestra aplicación estructurada y conectada a una base de datos, el siguiente paso crítico es asegurar el acceso. Este capítulo se centra en la Autenticación (AuthN - verificar quién es el usuario) y la Autorización (AuthZ - verificar qué puede hacer el usuario), implementadas principalmente en la capa de adaptadores web (Hono middleware), interactuando con el núcleo solo cuando sea necesario.

< Volver al Índice --- < Capítulo 4: Arquitectura --- Capítulo 6: E2E y Despliegue >

Estrategias de Autenticación

Existen varias formas de verificar la identidad de un usuario o cliente que consume tu API. Veremos dos enfoques populares en el contexto de Workers: JWT y Cloudflare Access.

Alternativa 1: JWT (JSON Web Tokens)

Los JWT son un estándar abierto (RFC 7519) para crear tokens de acceso que afirman ciertas declaraciones (claims). Son comúnmente usados en APIs REST porque son stateless: el servidor no necesita almacenar información de sesión, solo verificar la firma del token.

  • Flujo Básico:

    1. Login: El usuario se autentica (ej: con usuario/contraseña en un endpoint /login).
    2. Generación: Si las credenciales son válidas, el servidor genera un JWT firmado con una clave secreta. El JWT contiene información del usuario (ID, rol, etc.) y una fecha de expiración.
    3. Envío: El servidor devuelve el JWT al cliente.
    4. Uso: El cliente envía el JWT en el header Authorization (normalmente como Bearer <token>) en cada petición subsiguiente a rutas protegidas.
    5. Verificación: El servidor (un middleware) intercepta la petición, extrae el token, verifica su firma usando la clave secreta y comprueba su validez (ej: expiración). Si es válido, permite el acceso; si no, devuelve un error 401 (Unauthorized).
  • Implementación con Hono (@hono/jwt): Hono proporciona un middleware oficial para facilitar el trabajo con JWT.

    bun add @hono/jwt
    // src/infrastructure/web/middleware/auth.middleware.ts (Ejemplo)
    import { Hono } from 'hono';
    import { jwt, sign, verify } from 'hono/jwt';
    import type { Env } from '../../index'; // Asumiendo Env en index.ts
    import type { UserPayload } from '../../../core/users/domain/user.entity'; // Asumir tipo User
    
    // Interfaz para el payload del JWT
    interface JwtPayload {
      userId: number;
      role: string; // Ejemplo: 'admin', 'user'
      // otros campos...
      exp: number; // Expiración (timestamp UNIX)
    }
    
    // Función para generar un token (usada en el endpoint de login)
    export async function generateToken(payload: Omit<JwtPayload, 'exp'>, secret: string): Promise<string> {
      const enrichedPayload: JwtPayload = {
        ...payload,
        exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24) // Expira en 24 horas
      };
      return await sign(enrichedPayload, secret);
    }
    
    // Middleware Hono para verificar el token
    export const jwtAuthMiddleware = (secret: string) => {
      return jwt({
        secret: secret,
        // cookie: 'auth_token', // Opcional: buscar token en cookie en lugar de header
        async success(c, payload) {
           // ¡Éxito! El payload verificado está disponible.
           // Adjuntarlo al contexto para uso posterior en rutas/autorización.
           console.log(`JWT Válido para userId: ${payload.userId}`);
           c.set('user', payload as UserPayload); // Asegúrate que UserPayload coincida
        },
        async error(c, err) {
           // Error de verificación (token inválido, expirado, etc.)
           console.error("Error JWT:", err.message);
           return c.json({ error: 'Unauthorized', message: err.message }, 401);
        }
      });
    };
    
    // En src/index.ts, aplicarías el middleware a rutas protegidas:
    // const JWT_SECRET = env.JWT_SECRET; // ¡Obtener de secretos de Worker!
    // if (!JWT_SECRET) throw new Error("JWT_SECRET no configurado!");
    // app.use('/api/protected/*', jwtAuthMiddleware(JWT_SECRET));

    Seguridad JWT:

    • Secreto Robusto: Usa un secreto largo y aleatorio, almacenado de forma segura con wrangler secret put. ¡Nunca lo hardcodees!
    • HTTPS: Siempre usa HTTPS para transmitir tokens.
    • Expiración Corta: Establece tiempos de expiración razonables y considera usar refresh tokens para sesiones más largas.
    • Algoritmo: Usa algoritmos fuertes como HS256 (si el secreto es compartido) o RS256 (si usas claves pública/privada). Hono/jwt usa HS256 por defecto.
    • No Datos Sensibles: No incluyas información muy sensible directamente en el payload.
  • Pros y Contras JWT:

    • Pros: Stateless, bueno para APIs distribuidas, estándar ampliamente adoptado.
    • Cons: La invalidación antes de la expiración es compleja (requiere listas negras), el tamaño del token puede crecer si incluyes muchos claims.

Alternativa 2: Cloudflare Access / Gateway

Cloudflare ofrece servicios que pueden manejar la autenticación antes de que la petición llegue a tu Worker.

  • Cloudflare Access:

    • Cómo funciona: Protege aplicaciones (incluyendo Workers) requiriendo autenticación a través de proveedores de identidad (Google, GitHub, Okta, etc.) o métodos propios de Cloudflare (One-time PIN). Configuras políticas en el dashboard de Cloudflare para definir quién puede acceder.
    • Integración con Worker: Si la autenticación es exitosa, Cloudflare Access inyecta un JWT especial en el header CF-Access-JWT-Assertion de la petición que llega a tu Worker. Tu Worker puede (¡y debería!) verificar este JWT usando las claves públicas de Cloudflare para confiar en la identidad del usuario proporcionada por Access.
    • Implementación: La mayor parte es configuración en Cloudflare. En el Worker, necesitas un middleware para:
      1. Obtener el token CF-Access-JWT-Assertion.
      2. Obtener las claves públicas de tu tenant de Access (https://<your-tenant>.cloudflareaccess.com/cdn-cgi/access/certs).
      3. Verificar el token usando esas claves (puedes usar hono/jwt u otra librería JWT).
      4. Extraer la información del usuario (email, idp, etc.) del payload y adjuntarla al contexto.
    • Pros: Delega la autenticación compleja a Cloudflare, soporta SSO y múltiples IdPs, puede aplicar políticas de acceso granulares (país, IP, etc.).
    • Cons: Dependencia de Cloudflare, la verificación del JWT de Access en el Worker sigue siendo necesaria para seguridad zero-trust.
  • Cloudflare API Shield / Gateway (mTLS):

    • Cómo funciona: Puedes configurar Cloudflare para requerir Autenticación Mutua TLS (mTLS) para acceder a tu Worker. El cliente debe presentar un certificado TLS válido emitido por una CA en la que confíes. Esto es común para comunicaciones seguras entre servicios (M2M).
    • Integración con Worker: Cloudflare verifica el certificado del cliente. Puedes configurar que reenvíe información del certificado validado a tu Worker en headers específicos (ej: Cf-Tls-Client-Cert-Verified). Tu Worker simplemente necesita verificar la presencia y el valor de ese header para autorizar la petición.
    • Pros: Seguridad muy alta para M2M, gestionado por Cloudflare.
    • Cons: No práctico para autenticación de usuarios finales (navegadores), requiere gestión de certificados de cliente.
  • ¿Cuál elegir?

    • JWT: Bueno si controlas la generación de usuarios/tokens (login propio) y quieres una solución estándar y stateless.
    • Cloudflare Access: Excelente si quieres delegar la autenticación a proveedores externos, necesitas SSO, o quieres políticas de acceso avanzadas gestionadas por Cloudflare. Ideal para proteger aplicaciones internas o B2B.
    • mTLS (Gateway): Mejor para asegurar la comunicación entre tus propios servicios o con partners de confianza.

Alternativa 3: OAuth 2.0 / OpenID Connect (OIDC)

OAuth 2.0 es un framework de autorización que permite a una aplicación obtener acceso limitado a recursos en nombre de un usuario, sin exponer sus credenciales. OpenID Connect (OIDC) se construye sobre OAuth 2.0 para añadir una capa de identidad, permitiendo verificar quién es el usuario.

En el contexto de una API en Cloudflare Workers, típicamente tu API actúa como un “Resource Server” que necesita validar Access Tokens presentados por los clientes (ej: una SPA, una app móvil, otro backend). Tu Worker generalmente no implementa el flujo completo de OAuth 2.0 (como el Authorization Server), sino que confía en un Authorization Server externo o propio.

  • Flujo Típico (desde la perspectiva de la API Worker):

    1. Obtención del Token (Cliente): El cliente (SPA, móvil) realiza un flujo OAuth 2.0 (ej: Authorization Code Flow) con un Authorization Server (Google, Auth0, Okta, tu propio servidor de identidad) para obtener un Access Token.
    2. Llamada a la API: El cliente llama a tu API Worker, incluyendo el Access Token en el header Authorization: Bearer <access_token>.
    3. Validación del Token (Worker API): Tu Worker (a través de un middleware) necesita validar este Access Token. Hay dos métodos principales:
      • Token Introspection: El Worker llama a un endpoint de introspección (/introspect) expuesto por el Authorization Server, enviando el token. El Authorization Server responde si el token es válido y opcionalmente devuelve información asociada (usuario, scope). Desventaja: Añade latencia por la llamada externa.
      • Validación Local de JWT: Si el Authorization Server emite Access Tokens en formato JWT (muy común), tu Worker puede validarlos localmente. Necesita:
        • Obtener las claves públicas del Authorization Server (normalmente de un endpoint JWKS - JSON Web Key Set).
        • Verificar la firma del JWT usando la clave pública correspondiente.
        • Verificar las claims estándar (expiración exp, audiencia aud, emisor iss).
        • Ventaja: Mucho más rápido al no requerir llamada externa por cada petición.
    4. Extracción de Información: Si el token es válido, el middleware extrae la información relevante (ID de usuario sub, scopes scope, etc.) y la adjunta al contexto (c.set('user', ...) o c.set('tokenPayload', ...)). Si se usa OIDC, podría haber un ID Token (también JWT) con más detalles del usuario, o se podría llamar al endpoint UserInfo del Authorization Server.
  • Implementación en Hono/Workers:

    • Extracción: Un middleware simple extrae el token del header Authorization.
    • Validación JWT: Si son JWTs, puedes usar @hono/jwt, pero configurándolo para validar contra las claves públicas del Authorization Server (obtenidas del JWKS endpoint) y verificando iss y aud.
      // Ejemplo conceptual middleware validación JWT OIDC/OAuth2
      import { jwt } from 'hono/jwt'
      import { fetchJwks } from './utils/jwks'; // Función helper para obtener y cachear JWKS
      
      const OAUTH_ISSUER = 'https://your-auth-server.com/'; // En env/secrets
      const OAUTH_AUDIENCE = 'your-api-audience-identifier'; // En env/secrets
      
      export const oauthJwtValidator = () => {
        return async (c: any, next: Function) => {
          try {
            const authHeader = c.req.header('Authorization');
            if (!authHeader || !authHeader.startsWith('Bearer ')) {
              return c.json({ error: 'Unauthorized', message: 'Missing or invalid Bearer token' }, 401);
            }
            const token = authHeader.substring(7);
      
            // Obtener JWKS (con caché)
            const jwks = await fetchJwks(`${OAUTH_ISSUER}.well-known/jwks.json`);
            if (!jwks) throw new Error('Could not fetch JWKS');
      
            // Usar verify manualmente o adaptar hono/jwt si es posible
            // Aquí se necesitaría encontrar la clave correcta (kid) y verificar
            // const payload = await verify(token, /* clave correcta de jwks */, /* algoritmo */);
      
            // --- Alternativa: Usar librería específica como 'oauth4webapi' ---
            // const { payload } = await jwtVerify(token, /* JWKSSet */, {
            //   issuer: OAUTH_ISSUER,
            //   audience: OAUTH_AUDIENCE,
            // });
            // --- Fin Alternativa ---
      
            // TODO: Implementar validación JWT real usando JWKS y librería adecuada
            const payload = { /* Payload decodificado y validado */ sub: 'user123', scope: 'read:tasks', iss: OAUTH_ISSUER, aud: OAUTH_AUDIENCE }; // Placeholder
      
            // Validar issuer y audience manualmente si la librería no lo hace
            if (payload.iss !== OAUTH_ISSUER) throw new Error('Invalid issuer');
            if (payload.aud !== OAUTH_AUDIENCE && !(Array.isArray(payload.aud) && payload.aud.includes(OAUTH_AUDIENCE))) {
               throw new Error('Invalid audience');
            }
      
            console.log(`OAuth Token válido para sub: ${payload.sub}`);
            c.set('tokenPayload', payload); // Adjuntar payload validado
            await next();
      
          } catch (error: any) {
            console.error("Error validando OAuth token:", error.message);
            return c.json({ error: 'Unauthorized', message: `Token validation failed: ${error.message}` }, 401);
          }
        };
      };
      (Nota: La validación JWT con JWKS requiere lógica adicional para encontrar la clave correcta (kid) y usarla. Librerías como jose o oauth4webapi pueden ayudar).
    • Introspection: Necesitarías hacer una llamada fetch al endpoint de introspección del Authorization Server, enviando el token y las credenciales del cliente API (si son requeridas), y procesar la respuesta.
  • Seguridad:

    • Valida siempre iss (emisor) y aud (audiencia) para asegurar que el token es para tu API y fue emitido por el Authorization Server esperado.
    • Usa las URLs .well-known/openid-configuration o .well-known/oauth-authorization-server para descubrir dinámicamente los endpoints JWKS o de introspección.
    • Almacena client_id y client_secret (si usas introspección) de forma segura usando wrangler secret.
  • Pros y Contras OAuth 2.0 / OIDC:

    • Pros: Estándar de la industria, ideal para acceso de terceros y autenticación federada, separa la autenticación de tu API.
    • Cons: El flujo completo es complejo (aunque tu API solo valida tokens), la validación local de JWT requiere manejo de JWKS, la introspección añade latencia.

¿Cuál Elegir? (Actualizado)

  • JWT Propio: Bueno si controlas el login/usuarios…
  • Cloudflare Access: Excelente si quieres delegar auth a IdPs externos…
  • OAuth 2.0 / OIDC: La mejor opción si necesitas que aplicaciones de terceros accedan a tu API en nombre de usuarios, si quieres usar IdPs externos de forma estándar, o si construyes tu propio Authorization Server separado.
  • mTLS (Gateway): Para comunicación segura entre servicios (M2M).

Autorización (¿Qué puede hacer el usuario?)

Una vez que sabes quién es el usuario (Autenticación), necesitas determinar si tiene permiso para realizar la acción solicitada (Autorización).

  • Concepto: Role-Based Access Control (RBAC): Un enfoque común es asignar roles a los usuarios (ej: admin, editor, viewer). Luego, defines qué permisos tiene cada rol para acceder a ciertos recursos o realizar acciones.

  • Implementación con Middleware Hono: Creamos middleware que se ejecuta después del middleware de autenticación y verifica los roles/permisos del usuario (que obtuvimos del JWT o de Access y adjuntamos al contexto).

    // src/infrastructure/web/middleware/authorization.middleware.ts (Ejemplo)
    import { Hono } from 'hono';
    import type { UserPayload } from '../../../core/users/domain/user.entity'; // Tipo del usuario en contexto
    
    // Middleware Factory: Crea middleware que requiere un rol específico
    export const requireRole = (requiredRole: string) => {
      return async (c: any, next: Function) => { // Tipar 'c' adecuadamente con Variables
        const user = c.get('user') as UserPayload | undefined;
    
        if (!user) {
          // Esto no debería pasar si el middleware AuthN se ejecutó antes
          console.error("Error de autorización: Usuario no encontrado en contexto.");
          return c.json({ error: 'Forbidden: Authentication required' }, 403);
        }
    
        // Comprobar si el rol del usuario coincide (o si tiene el permiso)
        // En un sistema real, podrías tener una lista de roles: user.roles.includes(requiredRole)
        if (user.role !== requiredRole) {
          console.warn(`Acceso denegado para userId ${user.userId}. Rol requerido: ${requiredRole}, Rol actual: ${user.role}`);
          return c.json({ error: 'Forbidden: Insufficient permissions' }, 403);
        }
    
        // ¡Éxito! El usuario tiene el rol requerido. Continuar.
        console.log(`Acceso autorizado para userId ${user.userId} con rol ${user.role}`);
        await next();
      };
    };
    
    // Uso en src/index.ts (o en controladores/sub-apps Hono):
    // Asumiendo que jwtAuthMiddleware ya se aplicó antes a '/api/admin/*'
    // const JWT_SECRET = env.JWT_SECRET!;
    // app.use('/api/admin/*', jwtAuthMiddleware(JWT_SECRET));
    // app.use('/api/admin/*', requireRole('admin')); // Aplicar después de AuthN
    
    // Luego las rutas específicas:
    // adminApp.get('/users', async (c) => { ... }); // Solo accesible por admins

    Este middleware verifica si el user en el contexto tiene el requiredRole.

  • Obtención de Roles/Permisos:

    • JWT: Incluye el rol o permisos directamente en el payload del JWT al generarlo. Simple pero puede hacer el token grande y la información puede volverse obsoleta si los permisos cambian frecuentemente.
    • Base de Datos: Tras verificar el JWT/Access, usa el userId para consultar la base de datos (a través del servicio/repositorio de usuarios) y obtener los roles/permisos actualizados del usuario. Más flexible pero añade una consulta a la BD.

Seguridad Avanzada y Buenas Prácticas (OWASP)

Más allá de AuthN/AuthZ:

  • Input Validation: ¡Fundamental! Ya lo vimos con Zod. Valida todo lo que entra: body, query params, path params, headers. Previene inyecciones (SQL, NoSQL, Command), XSS, etc.
  • Rate Limiting: Protege contra ataques de fuerza bruta y DoS. Cloudflare ofrece Rate Limiting configurable en el dashboard, que actúa antes de que la petición llegue a tu Worker. Es la forma más eficiente. Si necesitas lógica más compleja, puedes implementarla en el Worker (ej: usando KV para contadores), pero es menos eficiente.
  • Secure Headers: Ya vimos X-Content-Type-Options, X-Frame-Options, X-XSS-Protection. Considera añadir Content-Security-Policy (CSP) si tu Worker sirve HTML, y Strict-Transport-Security (HSTS). Hono tiene middleware de Secure Headers.
  • Secret Management: Usa wrangler secret para toda información sensible (claves API, secretos JWT, credenciales DB si no usas bindings directos). Rota secretos periódicamente.
  • Dependency Security: Mantén las dependencias actualizadas y usa bun audit (o npm audit) regularmente.
  • HTTPS: Cloudflare lo gestiona por defecto entre el cliente y el Edge, pero asegúrate de que cualquier llamada saliente desde tu Worker a otras APIs también use HTTPS.
  • Manejo de Errores: No reveles información sensible (stack traces, mensajes de error internos) en las respuestas de error al cliente. Loguea detalles internamente y devuelve mensajes genéricos.

Testing de Seguridad

  • AuthN Middleware: Escribe tests (usando app.fetch como en Cap 2) que envíen peticiones:
    • Sin token -> Espera 401.
    • Con token inválido/expirado -> Espera 401.
    • Con token válido -> Espera que llame a next() (o verifica que c.get('user') se establece si puedes acceder al contexto en tests).
  • AuthZ Middleware: Escribe tests que envíen peticiones con un token válido pero simulando diferentes user payloads en el contexto:
    • Usuario con rol incorrecto -> Espera 403.
    • Usuario con rol correcto -> Espera que llame a next() (o que la ruta final devuelva 200).
  • Input Validation: Ya cubierto por las pruebas de validación de Zod.
  • Considerar Herramientas: Para aplicaciones críticas, considera herramientas de análisis estático de seguridad (SAST) o incluso pruebas de penetración, aunque esto va más allá de las pruebas unitarias/integración habituales.

Implementar seguridad es un proceso continuo. Hemos cubierto los fundamentos de autenticación y autorización en Workers/Hono, junto con prácticas esenciales. En el siguiente capítulo, nos enfocaremos en las pruebas End-to-End y el proceso de despliegue seguro a producción.

< Volver al Índice --- < Capítulo 4: Arquitectura --- Capítulo 6: E2E y Despliegue >

</rewritten_file>