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:
- AccessService: Gestiona la emisión, validación y renovación de tokens
- 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
- El usuario proporciona credenciales (email/password o apiKey)
- El sistema valida las credenciales contra el sistema legacy Quartup
- Si son válidas, genera un JWT con los datos del usuario y la sesión
- También genera un refresh token vinculado a un UUID en la base de datos
- El sistema devuelve ambos tokens al cliente
- En solicitudes posteriores, el cliente incluye el token en la cabecera Authorization
- 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
qupapiaccpara 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:
- apiAuth: Autenticación basada en JWT para clientes API externos
- 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
- Verifica la IP del cliente en cada solicitud
- Consulta la base de datos para obtener el recuento actual de solicitudes
- Si el cliente ha excedido el límite, devuelve un error 429 (Too Many Requests)
- Si no, incrementa el contador y permite que la solicitud continúe
- 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:
- ValidaRequest: Framework principal de validación basado en reglas
- Validadores especializados: Para casos específicos como identificadores fiscales
- 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íoNumericRule: Valida que un campo sea numéricoEmailRule: Valida que un campo sea un email válidoFiscalRule: 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:
- Uso de parámetros preparados en consultas
- Escape de valores de entrada
- 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