Etiqueta: mejores-prácticas

  • Seguridad de sesiones y cookies en PHP: guía práctica

    Seguridad de sesiones y cookies en PHP: guía práctica

    Resumen: Esta guía práctica resume las medidas esenciales para gestionar sesiones y cookies en PHP de forma segura: inicio de sesión, regeneración de ID, cookies seguras, timeouts, almacenamiento eficiente y limpieza al cerrar sesión.

    Introducción

    Sesiones y cookies permiten mantener estado en aplicaciones web PHP: autenticación, preferencias y datos temporales. Usadas correctamente mejoran la experiencia; mal configuradas, introducen riesgos de seguridad.

    Prerrequisitos

    Antes de aplicar las prácticas descritas asegúrate de servir las páginas sensibles por HTTPS y de tener control sobre el código que inicia y destruye sesiones. No afirmaré compatibilidades concretas; valida esto según tu entorno.

    Conocimiento mínimo necesario: saber dónde llamar a session_start() y cómo enviar cookies desde PHP. Evita exponer identificadores de sesión en URLs o registros.

    Desarrollo

    Procedimiento

    Pasos prácticos y concisos para asegurar sesiones y cookies en PHP. Aplique cada paso según el riesgo y la arquitectura de su aplicación.

    1. Iniciar sesiones de forma controlada y consistente.
    2. Regenerar el identificador de sesión tras la autenticación.
    3. Enviar cookies con flags Secure, HttpOnly y SameSite cuando corresponda.
    4. Implementar timeouts por inactividad y limpieza al logout.
    5. Almacenar solo identificadores en la sesión; datos pesados en base de datos o cache.
    6. Opcional: usar un session handler personalizado para almacenamiento centralizado.

    Detalles clave: regenerar el ID reduce el riesgo de session fixation; Secure y HttpOnly protegen el cookie en tránsito y frente a JavaScript; SameSite mitiga ciertas CSRF.

    Pro tip: Usa sesiones para identificar entidades (IDs) y no para persistir grandes objetos o datos sensibles sin cifrado.

    Ejemplos

    A continuación se incluyen ejemplos prácticos adaptados desde patrones comunes. Escapa los marcadores de apertura PHP en tu código según la plantilla del bloque.

    <?php
    // Start the session
    session_start();
    
    // Store some data in the session
    $_SESSION['username'] = 'JohnDoe';
    
    // Retrieve data from the session
    echo 'Hello, ' . $_SESSION['username']; // Outputs: Hello, JohnDoe
    Lenguaje del código: PHP (php)

    Regenerar el ID de sesión inmediatamente después de un login exitoso:

    <?php
    session_start();
    
    // After a successful login
    if ($_POST['username'] == 'user' && $_POST['password'] == 'password') {
        session_regenerate_id(true);  // Regenerate the session ID for added security
        
        $_SESSION['username'] = 'user';
        echo "Login successful, and your session ID has been regenerated!";
    }
    Lenguaje del código: PHP (php)

    Ejemplo de cookie segura con atributos recomendados (transmisión segura y protección contra acceso por JS):

    <?php
    // Set a cookie that only works on secure connections (HTTPS)
    setcookie('user_preference', 'dark_mode', [
        'expires' => time() + 3600, // 1 hour expiration
        'path' => '/',
        'domain' => 'yourdomain.com',
        'secure' => true,  // Send cookie only over HTTPS
        'httponly' => true,  // Prevent JavaScript access to cookie
        'samesite' => 'Strict' // Helps prevent CSRF attacks
    ]);
    
    echo 'Your preference cookie is set securely.';
    Lenguaje del código: PHP (php)

    Timeout de sesión por inactividad (mecanismo simple basado en marcas temporales):

    <?php
    session_start();
    
    // Set timeout period (10 minutes)
    $timeout = 600;
    
    if (isset($_SESSION['LAST_ACTIVITY']) && (time() - $_SESSION['LAST_ACTIVITY'] > $timeout)) {
        // If the session has expired, destroy it
        session_unset();
        session_destroy();
        echo 'Session expired. Please log in again.';
    } else {
        $_SESSION['LAST_ACTIVITY'] = time(); // Update last activity time
        echo 'Your session is still active.';
    }
    Lenguaje del código: PHP (php)

    Esqueleto de un session handler personalizado para almacenar sesiones en base de datos o cache centralizada:

    <?php
    class CustomSessionHandler extends SessionHandler {
        public function read($session_id) {
            // Read session data from the database
        }
    
        public function write($session_id, $data) {
            // Write session data to the database
        }
    }
    
    // Register custom session handler
    $handler = new CustomSessionHandler();
    session_set_save_handler($handler, true);
    session_start();
    Lenguaje del código: PHP (php)

    Al cerrar sesión, destruye la sesión y expira las cookies relevantes para evitar reutilización:

    <?php
    session_start();
    
    // Destroy the session and clear all session data
    session_unset();
    session_destroy();
    
    // Expire session and preference cookies
    setcookie(session_name(), '', time() - 3600, '/');
    setcookie('user_preference', '', time() - 3600, '/');
    
    echo 'Session and cookies have been cleared.';
    Lenguaje del código: PHP (php)

    Checklist

    1. Servir páginas sensibles por HTTPS.
    2. Iniciar sesión con session_start() solo donde sea necesario.
    3. Regenerar ID de sesión tras autenticación (session_regenerate_id).
    4. Enviar cookies con Secure, HttpOnly y SameSite adecuados.
    5. Implementar timeout por inactividad y actualizar LAST_ACTIVITY.
    6. Almacenar solo identificadores en la sesión; consultar DB para datos grandes.
    7. Destruir sesión y expirar cookies en logout.

    Conclusión

    La seguridad de sesiones y cookies es una combinación de buenas prácticas: transporte seguro (HTTPS), configuración correcta de cookies, rotación de identificadores, timeouts y limpieza al logout. Estas medidas reducen riesgos comunes como hijacking y XSS.

    Empieza aplicando los pasos del checklist y adapta las implementaciones (por ejemplo, handlers personalizados) según la escala y arquitectura de tu aplicación.

  • Registrar cada llamada API sin ralentizar el servidor

    Registrar cada llamada API sin ralentizar el servidor

    Resumen: Cómo registrar cada llamada a tu API sin degradar el rendimiento. Enfoque práctico: logging asíncrono, buffering, offload a workers, enmascarado de datos y rotación.

    Introducción

    Registrar cada petición API es crítico para depuración, auditoría y análisis, pero hacerlo mal puede convertir el logging en el principal cuello de botella.

    La regla de oro: nunca hagas que el manejador principal espere a que los logs se escriban. El registro debe ser asíncrono y, preferiblemente, salir del camino de ejecución principal.

    Prerrequisitos

    Los ejemplos usan Node.js y bibliotecas comunes; adapta las ideas a otros runtimes si es necesario.

    • Dependencia de un logger de alto rendimiento (p. ej. Pino).
    • Mecanismo para buffering o una cola (memoria, Redis, Kafka).
    • Worker o servicio externo para persistencia y envío a sistemas centralizados.

    Desarrollo

    Procedimiento

    Paso 1 — Decide qué registrar: evita sobre-logging. Campos típicos: timestamp, método, ruta, status, latencia, IP, userId, requestId y payload con datos sensibles enmascarados.

    {
      "time": "2025-08-11T14:23:45.123Z",
      "method": "POST",
      "url": "/api/orders",
      "status": 201,
      "responseTimeMs": 123,
      "userId": "usr_1a2b3c",
      "ip": "203.0.113.42",
      "userAgent": "Mozilla/5.0 (Macintosh...)",
      "requestId": "req_abcd1234"
    }
    Lenguaje del código: JSON / JSON con comentarios (json)

    Paso 2 — Usa un logger no bloqueante. Olvida console.log en producción; emplea un logger diseñado para escritura asíncrona y serialización rápida.

    const pino = require('pino');
    const logger = pino({
      level: 'info',
      transport: {
        target: 'pino-pretty'
      }
    });
    
    app.use((req, res, next) => {
      const start = Date.now();
      
      res.on('finish', () => {
        logger.info({
          method: req.method,
          url: req.originalUrl,
          status: res.statusCode,
          responseTime: Date.now() - start
        });
      });
      
      next();
    });
    Lenguaje del código: JavaScript (javascript)

    Paso 3 — Bufferiza en memoria y envía en lotes. Convierte miles de escrituras pequeñas en pocas escrituras grandes.

    let logBuffer = [];
    const BUFFER_SIZE = 50;
    const FLUSH_INTERVAL = 5000; // ms
    
    function flushLogs() {
      if (logBuffer.length > 0) {
        logger.info({ batch: logBuffer });
        logBuffer = [];
      }
    }
    
    setInterval(flushLogs, FLUSH_INTERVAL);
    
    app.use((req, res, next) => {
      const start = Date.now();
    
      res.on('finish', () => {
        logBuffer.push({
          method: req.method,
          url: req.originalUrl,
          status: res.statusCode,
          time: Date.now(),
          latency: Date.now() - start
        });
    
        if (logBuffer.length >= BUFFER_SIZE) {
          flushLogs();
        }
      });
    
      next();
    });
    Lenguaje del código: JavaScript (javascript)

    Paso 4 — Desacopla mediante cola y workers: para tráfico intenso, envía logs a una cola y procesa desde procesos independientes.

    const Redis = require('ioredis');
    const pub = new Redis();
    
    app.use((req, res, next) => {
      const start = Date.now();
    
      res.on('finish', () => {
        pub.publish('api_logs', JSON.stringify({
          method: req.method,
          url: req.originalUrl,
          status: res.statusCode,
          latency: Date.now() - start
        }));
      });
    
      next();
    });
    Lenguaje del código: JavaScript (javascript)
    const sub = new Redis();
    sub.subscribe('api_logs');
    sub.on('message', (channel, message) => {
      const logData = JSON.parse(message);
      // Persistir logData de forma asíncrona (BD, ELK, S3...)
    });
    Lenguaje del código: JavaScript (javascript)

    Paso 5 — Envío remoto asíncrono: si mandas logs a sistemas como ELK o CloudWatch, hazlo en lotes o por workers para evitar latencia en el request path.

    const winston = require('winston');
    require('winston-cloudwatch');
    
    winston.add(new winston.transports.CloudWatch({
      logGroupName: 'my-api-logs',
      logStreamName: 'production',
      awsRegion: 'us-east-1',
      jsonMessage: true
    }));
    
    app.use((req, res, next) => {
      const start = Date.now();
      res.on('finish', () => {
        winston.info({
          method: req.method,
          url: req.originalUrl,
          status: res.statusCode,
          latency: Date.now() - start
        });
      });
      next();
    });
    Lenguaje del código: JavaScript (javascript)

    Paso 6 — Enmascara datos sensibles antes de loguear: nunca escribas contraseñas, números de tarjeta o tokens sin protección.

    function maskSensitive(obj) {
      const clone = { ...obj };
      if (clone.password) clone.password = '******';
      if (clone.cardNumber) clone.cardNumber = '**** **** **** ' + clone.cardNumber.slice(-4);
      return clone;
    }
    
    app.use(express.json());
    app.use((req, res, next) => {
      req.body = maskSensitive(req.body);
      next();
    });
    Lenguaje del código: JavaScript (javascript)

    Paso 7 — Mide y ajusta: compara latencias y uso de CPU/memoria antes y después del cambio. Usa herramientas de carga y APM para validar impacto.

    autocannon -c 100 -d 30 http://localhost:3000
    Lenguaje del código: Bash (bash)

    Paso 8 — Rotación y archivo: configura rotación por tamaño o día y mueve logs antiguos a almacenamiento económico.

    pino server.js | tee >(pino-pretty) | rotatelogs ./logs/api-%Y-%m-%d.log 86400
    Lenguaje del código: Bash (bash)

    Paso 9 — Distribuye a gran escala: evita escribir localmente, usa shippers (Filebeat/Fluent Bit), Kafka para ingesta masiva y sampling para reducir volumen de success logs.

    Paso 10 — Arquitectura de producción (resumen): gateway → collectors/colas → workers → almacenamiento indexable → dashboards y alertas.

    Ejemplos

    Los ejemplos anteriores cubren patrones comunes: logger rápido (Pino), buffering en proceso, publicación a Redis y consumo por workers, y envío asíncrono a servicios remotos.

    Checklist

    1. No bloquear el request path: logging asíncrono.
    2. Usar un logger de alto rendimiento (p. ej. Pino).
    3. Bufferizar y enviar en lotes.
    4. Offload a workers o colas para tráfico intenso.
    5. Enmascarar datos sensibles antes de persistir.
    6. Rotación y archivado de logs.
    7. Medir impacto y ajustar frecuencias/ tamaños de lote.

    Conclusión

    Registrar cada llamada API es viable si se diseña para no bloquear el flujo principal: emplea loggers rápidos, buffering, colas y workers; enmascara datos; y monitoriza el costo del logging.

    Aplica estas prácticas progresivamente y verifica con pruebas de carga y APM antes de desplegar en producción.