Auditoría de Seguridad y Protección OWASP en Web Apps con Next.js 15, Redis Rate Limiting y CSP Estricto

En el desarrollo de plataformas SaaS y aplicaciones empresariales modernas, la velocidad de desarrollo jamás debe comprometer la seguridad de la infraestructura y los datos de los usuarios. Con el auge de Next.js 15 Server Components y Server Actions, surgen nuevos vectores de ataque que van más allá del clásico Cross-Site Scripting (XSS) y SQL Injection. En este artículo detallamos las medidas técnicas obligatorias para auditar y blindar aplicaciones web cumpliendo los estándares de OWASP Top 10.

Principales Vectores de Vulnerabilidad en Next.js Moderno#
El framework Next.js ofrece defensas nativas sólidas, pero requiere configuración explícita en los siguientes frentes críticos:
- Ataques de Fuerza Bruta y DoS a Server Actions: Sin limitación de peticiones por IP/Usuario, los endpoints serverless pueden ser sobrecargados.
- Inyección de Scripts (XSS) y Clickjacking: Mitigados mediante encabezados HTTP de seguridad y un Content Security Policy (CSP) estricto con nonces criptográficos.
- Broken Object Level Authorization (BOLA / IDOR): Acceso no autorizado a registros modificando IDs en parámetros de mutaciones.
1. Rate Limiting Distribuido con Upstash Redis en Middleware#
Para prevenir ataques de fuerza bruta en rutas de login, recuperación de contraseñas y envío de formularios, implementamos un limitador de tasa basado en el algoritmo Sliding Window Counter:
// src/middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/request';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';
const redis = new Redis({
url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});
// Permitir 10 peticiones por ventana de 60 segundos por IP
const ratelimit = new Ratelimit({
redis: redis,
limiter: Ratelimit.slidingWindow(10, '60 s'),
analytics: true,
prefix: '@ratelimit/api',
});
export async function middleware(request: NextRequest) {
// Aplicar Rate Limiting a rutas de autenticación y mutaciones críticas
if (request.nextUrl.pathname.startsWith('/api/auth') || request.nextUrl.pathname.startsWith('/api/checkout')) {
const ip = request.ip ?? request.headers.get('x-forwarded-for') ?? '127.0.0.1';
const { success, limit, remaining, reset } = await ratelimit.limit(ip);
if (!success) {
return new NextResponse(
JSON.stringify({
error: 'Demasiadas solicitudes. Por favor intente más tarde.',
retryAfter: reset,
}),
{
status: 429,
headers: {
'Content-Type': 'application/json',
'X-RateLimit-Limit': limit.toString(),
'X-RateLimit-Remaining': remaining.toString(),
'X-RateLimit-Reset': reset.toString(),
},
}
);
}
}
return NextResponse.next();
}
export const config = {
matcher: ['/api/:path*'],
};
2. Content Security Policy (CSP) Estricto con Nonce Criptográfico#
Un CSP estricto prohíbe la ejecución de scripts inline maliciosos y restringe los orígenes de carga de recursos a dominios de confianza:
// src/lib/securityHeaders.ts
import { headers } from 'next/headers';
export function getSecurityHeaders() {
const nonce = Buffer.from(crypto.randomUUID()).toString('base64');
const cspHeader = `
default-src 'self';
script-src 'self' 'nonce-${nonce}' 'strict-dynamic' https: 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' blob: data: https://brayan.es;
font-src 'self';
object-src 'none';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
`.replace(/\s{2,}/g, ' ').trim();
return {
headers: {
'Content-Security-Policy': cspHeader,
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block',
'Referrer-Policy': 'strict-origin-when-cross-origin',
'Permissions-Policy': 'camera=(), microphone=(), geolocation=()',
},
nonce,
};
}
3. Blindaje de Server Actions contra BOLA / IDOR#
Toda Server Action debe validar estrictamente el esquema de entrada con Zod y comprobar la pertenencia del recurso (tenant-isolation):
// src/actions/invoiceActions.ts
'use server';
import { z } from 'zod';
import { prisma } from '@/lib/prisma';
import { getAuthenticatedUser } from '@/lib/auth';
const DeleteInvoiceSchema = z.object({
invoiceId: z.string().cuid(),
});
export async function deleteInvoiceAction(formData: FormData) {
// 1. Autenticación del usuario de la sesión
const user = await getAuthenticatedUser();
if (!user) {
throw new Error('No autorizado');
}
// 2. Validación de entrada tipada
const parsed = DeleteInvoiceSchema.safeParse({
invoiceId: formData.get('invoiceId'),
});
if (!parsed.success) {
return { success: false, error: 'Datos inválidos' };
}
// 3. Verificación de propiedad (Evita IDOR)
const invoice = await prisma.invoice.findFirst({
where: {
id: parsed.data.invoiceId,
organizationId: user.organizationId, // Aislamiento por inquilino
},
});
if (!invoice) {
return { success: false, error: 'Comprobante no encontrado o sin permisos' };
}
await prisma.invoice.delete({
where: { id: parsed.data.invoiceId },
});
return { success: true };
}
Checklist de Auditoría de Ciberseguridad Web#
- Rate Limiting: Implementado en endpoints de login, recuperación de clave y pagos.
- CSP y Encabezados:
X-Frame-Options: DENY,nosniffy CSP estricto activados. - Sanitización de Entradas: Validación rigurosa con esquemas Zod en todas las Server Actions y APIs.
- Aislamiento Multi-Tenant: Filtros RLS y validación de
organizationIden todas las consultas de base de datos. - Gestión Segura de Secretos: Variables de entorno protegidas sin exposición al cliente (
NEXT_PUBLIC_).
Conclusión#
Garantizar la seguridad de una aplicación web moderna exige una estrategia de defensa en profundidad, combinando Rate Limiting distribuido, aislamiento estricto de datos y encabezados HTTP robustos.
¿Necesitas auditar la seguridad de tu plataforma web o SaaS antes de salir a producción? Descubre nuestros servicios de Consultoría y Desarrollo de Software o contáctanos para agendar una auditoría técnica.
¿Te gustaría profundizar en estos temas?
Aprende sobre desarrollo de software, apps a medida, automatizaciones con N8N, Next.js y Cloud con casos reales.


