Skip to main content

6. Seguridad

6.1. Autenticación basada en JWT

Descripción General

El sistema implementa un mecanismo de autenticación basado en JSON Web Tokens (JWT) para gestionar la autenticación de usuarios de forma segura y stateless. Este enfoque permite mantener el estado de autenticación del usuario sin necesidad de consultar la base de datos en cada petición.

Arquitectura

El sistema utiliza un enfoque de doble token:

  • Access Token: Token principal para acceder a los recursos protegidos
  • Refresh Token: Token de larga duración para renovar el access token

Los tokens son firmados utilizando criptografía de clave asimétrica (RS256) con un par de claves pública/privada.

Implementación

La autenticación JWT se implementa principalmente en dos servicios:

  1. AccessService: Gestiona la emisión, validación y renovación de tokens
  2. AuthMiddleware: Verifica la presencia y validez del token en cada petición protegida

Generación de Tokens

public function loginByCredentials(Request $request)
{
$ko = Q::initSessionByCredentials($request->email, $request->password);
if (substr($ko, 0, 2) == 'ok') {
return $this->login($request);
}
throw new Exception('No autorizado', 401);
}

El método login() genera el token JWT utilizando la función helper tokenice():

protected function login(Request $request)
{
$data = [
"cod_empresa" => $_SESSION['quup']['quupempr']['cod_empresa'],
"id_tenant" => $_SESSION['quup']['quupempr']['id_tenant'],
"nombre_empresa" => $_SESSION['quup']['quupempr']['nombre'],
"nombre_usuario" => $_SESSION['quup']['quupusua']['nombre'],
"cod_usuario" => $_SESSION['quup']['quupusua']['cod_usuario'],
"cod_usuario_ext" => $_SESSION['quup']['quupusua']['cod_usuario_ext'],
"num_usuario" => $_SESSION['quup']['quupusua']['num_usuario']
];

$sessionId = session_id();
$data["session_id"] = $sessionId;
$token = tokenice($data, $this->tokenExpiresInSeconds);

// Código para generar el refresh token...

return ['token' => $token, 'refreshToken' => $refreshToken];
}

Flujo de Datos

  1. El usuario proporciona credenciales (email/password o apiKey)
  2. El sistema valida las credenciales contra el sistema legacy Quartup
  3. Si son válidas, genera un JWT con los datos del usuario y la sesión
  4. También genera un refresh token vinculado a un UUID en la base de datos
  5. El sistema devuelve ambos tokens al cliente
  6. En solicitudes posteriores, el cliente incluye el token en la cabecera Authorization
  7. El middleware de autenticación valida el token y restaura la sesión

Consideraciones Técnicas

  • Los tokens tienen periodos de expiración configurables en el archivo Config/token.php
  • El sistema usa un par de claves pública/privada almacenado en /keys/
  • Se mantiene un registro de los refresh tokens en la tabla qupapiacc para permitir su revocación

6.2. Middleware de autorización

Descripción General

El sistema utiliza el patrón middleware para implementar la lógica de autenticación y autorización, permitiendo proteger las rutas y recursos de manera flexible y modular.

Implementación

El middleware principal de autorización es App\Middlewares\AuthMiddleware, que verifica la autenticación del usuario antes de permitir el acceso a rutas protegidas.

namespace App\Middlewares;

use App\Core\Interfaces\Middleware;
use App\Core\RequestFactory as Request;
use App\Core\CoreQuartup as Q;
use App\Exceptions\UnauthorizedException;
use App\Exceptions\NotSameOriginException;
use function App\Helpers\decodeToken;

class AuthMiddleware implements Middleware
{
public function handle(Request $request, callable $next)
{
$authorizationHeader = $request->headers->get('Authorization');
$traitRefresh = $request->headers->get('TraitRefresh');

if ($authorizationHeader || $traitRefresh) {
return $this->apiAuth($request, $next);
} else {
return $this->innerAuth($request, $next);
}
}

// Implementación de apiAuth e innerAuth...
}

El middleware soporta dos modos de autenticación:

  1. apiAuth: Autenticación basada en JWT para clientes API externos
  2. innerAuth: Autenticación basada en cookies de sesión para comunicación interna

Aplicación de Middleware

La aplicación del middleware de autenticación se realiza en index.php:

$noAuth = Config::noAuthRoutes();
$pathInfo = trim($request->getPathInfo(), '/');
if (!in_array($pathInfo, $noAuth)) {
$kernel->addMiddleware(new Auth);
$kernel->addMiddleware(new RateLimitMiddleware);
$kernel->addMiddleware(new SwitchUtf8Latin1Middleware);
}

Las rutas que no requieren autenticación se definen en Config/noAuthRoutes.php:

return [
"auth/access",
"auth/access/by-api-key",
"auth/access/refresh",
"auth/google/callback",
"auth/google/with-g-token",
"auth/google/get-qupgoolou",
];

Consideraciones Técnicas

  • El middleware verifica el origen de la petición para prevenir ataques CSRF
  • Propaga los datos del usuario autenticado a través del objeto Request
  • Maneja diferentes tipos de autenticación de forma transparente

6.3. Rate limiting

Descripción General

El sistema implementa un mecanismo de limitación de tasa (rate limiting) para proteger la API contra abusos, ataques de fuerza bruta y sobrecarga de solicitudes.

Implementación

La funcionalidad de rate limiting se implementa a través del middleware RateLimitMiddleware, que limita el número de solicitudes por dirección IP en un período de tiempo determinado.

namespace App\Middlewares;

use App\Core\Interfaces\Middleware;
use App\Core\RequestFactory as Request;
use Exception;
use PDO;
use App\Helpers\Config;

class RateLimitMiddleware implements Middleware
{
protected $pdo;
protected $limit = 100; // Valor predeterminado
protected $duration = 60; // Valor predeterminado

public function __construct()
{
$climit = Config::rateLimit();
if (isset($climit['limit']) && is_numeric($climit['limit'])) {
$this->limit = $climit['limit'];
}
if (isset($climit['duration']) && is_numeric($climit['duration'])) {
$this->duration = $climit['duration'];
}
}

public function handle(Request $request, callable $next)
{
$this->connectDatabase();
$clientIp = $request->getClientIp();
$currentTime = time();

// Verificación de límites y procesamiento...

if ($row && $currentTime - $row['last_request_unix'] < $this->duration) {
if ($row['qupapiratlim_request_count'] >= $this->limit) {
throw new Exception('Too Many Requests', 429);
}
// Incrementar contador...
}

return $next($request);
}
}

Configuración

Los parámetros de rate limiting se configuran en App/Config/rateLimit.php:

return [
"limit" => 100, // Máximo de solicitudes permitidas
"duration" => 60 // Período de tiempo en segundos
];

Funcionamiento

  1. Verifica la IP del cliente en cada solicitud
  2. Consulta la base de datos para obtener el recuento actual de solicitudes
  3. Si el cliente ha excedido el límite, devuelve un error 429 (Too Many Requests)
  4. Si no, incrementa el contador y permite que la solicitud continúe
  5. Cuando expira la ventana de tiempo, reinicia el contador

Consideraciones Técnicas

  • Los datos de rate limiting se almacenan en la tabla qupapiratlim
  • El sistema detecta automáticamente si se está ejecutando en un entorno Docker
  • La configuración permite ajustar tanto el número de peticiones como la duración de la ventana

6.4. Validación y sanitización de datos

Descripción General

El sistema implementa un robusto mecanismo de validación de datos de entrada para garantizar la integridad, seguridad y coherencia de los datos que se procesan en la aplicación.

Arquitectura

La validación se implementa en múltiples niveles:

  1. ValidaRequest: Framework principal de validación basado en reglas
  2. Validadores especializados: Para casos específicos como identificadores fiscales
  3. Validación en DTOs: En la transformación de datos de entrada

Implementación Principal

El componente central es la clase App\Validations\ValidaRequest, que valida tanto encabezados como el cuerpo de la petición:

namespace App\Validations;

use App\Core\RequestFactory as Request;
use App\Exceptions\ValidationException;
use App\Validations\Rules\RuleFactory;

class ValidaRequest
{
protected $errors = [];
protected $request;
protected $rules = [
'body' => [],
'headers' => [],
];
protected $action;

public static function make(Request $request)
{
return new self($request);
}

public function validateBody(array $rules)
{
$this->rules['body'] = $rules;
return $this;
}

public function validateHeaders(array $rules)
{
$this->rules['headers'] = $rules;
return $this;
}

// Implementación de métodos de validación...
}

Reglas de Validación

Las reglas se definen en clases dedicadas dentro de App\Validations\Rules, como:

  • RequiredRule: Valida que un campo esté presente y no vacío
  • NumericRule: Valida que un campo sea numérico
  • EmailRule: Valida que un campo sea un email válido
  • FiscalRule: Valida identificadores fiscales

Uso en Controladores

La validación se aplica en los controladores, típicamente en los métodos de procesamiento de solicitudes:

public function store(Request $request)
{
ValidaRequest::make($request)
->validateHeaders([
'x-tienda' => 'required|numeric',
])
->validateBody([
'cart_id' => 'required|numeric',
'product_id' => 'required',
'price' => 'required:zeroable|numeric',
'discount' => 'required:zeroable|numeric',
'units' => 'required|numeric',
])
->action('store')
->handle();

return response()->json($this->CartsService->storeLine($request->all()), 201);
}

Validación Especializada

El sistema incluye validadores especializados para casos específicos, como la validación de identificadores fiscales:

namespace App\Validations\TaxValidations;

class TaxIdentifierValidator {
private $validator;
private $taxId;

public function __construct(string $countryCode, string $taxId) {
$this->validator = TaxValidatorFactory::create($countryCode);
$this->taxId = trim($taxId);
}

public function validate(): void {
$this->validator->validate($this->taxId);
}

public function getType(): string {
return $this->validator->type($this->taxId);
}
}

Consideraciones Técnicas

  • La validación maneja diferentes contextos (creación vs. actualización)
  • Los errores de validación se devuelven con código HTTP 400
  • El sistema incluye soporte para mensajes de error personalizados
  • La validación se aplica tanto a cabeceras como al cuerpo de la solicitud

6.5. Protección contra ataques comunes

Descripción General

El sistema implementa diversas medidas de seguridad para protegerse contra los ataques más comunes en aplicaciones web y APIs.

Protección CSRF

La API implementa verificación de origen para prevenir ataques Cross-Site Request Forgery:

public function isSameOrigin(Request $request): bool
{
// Obtenemos la URL base del servidor
$base = $request->getSchemeAndHttpHost();

// Obtenemos el valor del encabezado 'Origin' si está disponible
$origin = $request->headers->get('Origin');

if ($origin) {
// Comparamos el origen con la base
return $origin === $base;
}

// Si no hay 'Origin', intentamos con el 'Referer'
$referer = $request->headers->get('Referer');
if ($referer) {
// Parseamos el referer y comparamos el esquema y el host
$refererParts = parse_url($referer);
$refererSchemeAndHost = $refererParts['scheme'] . '://' . $refererParts['host'];
if (isset($refererParts['port'])){
$refererSchemeAndHost .= ':' . $refererParts['port'];
}
return $refererSchemeAndHost === $base;
}

// Si no hay ni 'Origin' ni 'Referer', devolvemos false
return false;
}

Prevención de acceso a archivos sensibles

El archivo .htaccess incluye reglas para prevenir el acceso directo a archivos sensibles:

# Denegar acceso directo a env.php
RewriteCond %{REQUEST_URI} \.json$
RewriteRule ^ - [F,L]

# Denegar acceso a archivos .md
RewriteCond %{REQUEST_URI} \.md$
RewriteRule ^ - [F,L]

Seguridad de las claves criptográficas

Las claves criptográficas utilizadas para firmar los tokens JWT están protegidas:

# Archivo: api2/keys/.htaccess
Deny from all

Encabezados de seguridad

La API configura encabezados de seguridad para prevenir varios tipos de ataques:

return [
'Content-Type' => 'application/json; charset=utf-8',
'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers' => 'Content-Type, Authorization, X-Requested-With, X-Application, X-Tienda, X-auth, Access-Control-Allow-Origin, Traitrefresh',
'Access-Control-Allow-Credentials' => "true",
];

Protección contra inyección SQL

El sistema implementa varias capas de protección contra inyección SQL:

  1. Uso de parámetros preparados en consultas
  2. Escape de valores de entrada
  3. Validación de tipos de datos
// Ejemplo de escape de valores
$safeValue = addslashes($value);

Detección de entornos

El sistema detecta automáticamente si se está ejecutando en un entorno de producción o desarrollo, ajustando las medidas de seguridad según corresponda:

QuApp::setEnvironment();
define('ENVIRONMENT', QuApp::getEnvironment());
if (ENVIRONMENT == 'DEV') {
$converter = new QuErrorsToException(E_ALL);
} else {
$converter = new QuErrorsToException(E_ALL & ~(E_NOTICE | E_WARNING));
}

Consideraciones Técnicas

  • En modo producción, los errores no se muestran directamente al usuario
  • Los tokens JWT utilizan un algoritmo seguro (RS256)
  • Las credenciales sensibles no se almacenan en archivos públicamente accesibles
  • El sistema incluye control de sesiones para prevenir robos de sesión