🧩 BladeCAPTCHA
MIT 9 KB No tracking

Esta página detalla cómo integrar y configurar BladeCAPTCHA en tu sitio web.

🕵️ Privacidad

No rastrea, no utiliza cookies, y no se conecta a servicios de terceros.

Accesibilidad

Compatible con lectores de pantalla, teclado y bajo contraste visual.

💻 Auto-hosteado

No dependés de APIs externas. Todo corre en tu servidor.

📦 Requisitos

📁 Instalación

  1. Descargá el paquete completo desde GitHub o cloná el repositorio.
  2. Subí el contenido a tu servidor.
  3. Renombrá config/config.sample.php a config/config.php y editá el valor de CAPTCHA_SECRET_KEY, que es la clave secreta con que se validan los tokens. (Más info sobre esta configuración aquí.)
mv config/config.sample.php config/config.php
nano config/config.php

🚀 Uso básico

Importar el módulo captcha.js y llamar a initCaptcha() con la configuración deseada:

<script type="module">
import { initCaptcha } from './captcha.js';
(async () => {
  try {
    await initCaptcha({
      mode: 'autoFormIntegration',
      // otros parámetros...
    });
  } catch (err) {
    console.error(err || err.message);
  }
})();
</script>

⚙️ Configuración (initCaptcha)

La función initCaptcha(options) acepta los siguientes parámetros:

Clave Tipo Obl / Opc Descripción
mode 'manualHandling' o 'autoFormIntegration' Obligatorio Define si la verificación se inicia manualmente (sin integración directa con formularios) o si se intercepta el envío de un formulario para añadir en él un campo oculto con el valor del token de manera automática.
formSelector string Obligatorio si mode = 'autoFormIntegration' Selector CSS del formulario a interceptar.
inputName string Obligatorio si mode = 'autoFormIntegration' Nombre del campo oculto donde se inyectará el token.
statusSelector string Opcional Selector del elemento en el que se mostrará el estado del CAPTCHA en formato de texto. Durante la ejecución se aplican estas clases CSS:
  • .loading: Mientras se espera respuesta del servidor.
  • .success: Cuando la verificación es exitosa.
  • .error: En caso de fallo.
  • .info: Durante el progreso numérico del desafío.
verifyButtonSelector string Obligatorio si mode = 'manualHandling' Selector del botón que inicia la verificación manual.
submitButtonSelector string Obligatorio si mode = 'autoFormIntegration' Selector del botón de envío del formulario.
apiBaseUrl string Opcional (por defecto: '../php') URL base para las llamadas a la API del backend (endpoints PHP). Útil si se usan servidores en otros dominios o rutas personalizadas; asegúrate de que la configuración CORS del backend permita el dominio del frontend (ver configuración).
onStart function Opcional Callback que se ejecuta al iniciar el proceso. Ideal para deshabilitar botones o mostrar un spinner.
onEnd function Opcional Callback para revertir el estado visual tras cancelar o finalizar el desafío.
onProgress function(p: number) Opcional Callback para informar el progreso de verificación (0–100).
manualHandlingAutoStartOnLoad boolean Opcional 🚀 Solo para manualHandling. Si es true, inicia la verificación apenas carga la página.
onSuccess function(token: string) Obligatorio para manualHandling Callback que se ejecuta al completar con éxito la verificación. Recibe el token para validación en el servidor. 🔑 Es esencial en integración manual, ya que allí no se inyecta el token automáticamente.
onError function(err: Error) Opcional Callback que se ejecuta cuando ocurre un error en la verificación. Recibe un objeto Error con la propiedad message para diagnóstico.

🔀 Ejemplo: autoFormIntegration

▶️ Ver demo

<script type="module">
import { initCaptcha } from './captcha.js';

(async () => {
  try {
    await initCaptcha({
      mode: 'autoFormIntegration',
      apiBaseUrl : '../php',
      formSelector: '#contactForm',
      inputName: 'captcha_token',
      submitButtonSelector: '#submitBtn',
      onStart: () => {
        document.querySelector('#submitBtn').textContent = 'Verificando...';
      },
      onEnd: () => {
        document.querySelector('#submitBtn').textContent = 'Enviar';
      }
    });
  } catch (err) {
     console.error(err || err.message);
  }
})();
</script>

🖐️ Ejemplo: manualHandling

▶️ Ver demo

<script type="module">
import { initCaptcha } from './captcha.js';

(async () => {
  try {
    await initCaptcha({
      mode: 'manualHandling',
      apiBaseUrl : '../php',
      verifyButtonSelector: '#checkHuman',
      statusSelector: '#captchaStatus',
      manualHandlingAutoStartOnLoad: false,
      onStart: () => {
        console.log('Iniciando verificación...');
      },
      onEnd: () => {
        console.log('Verificación cancelada o completada.');
      },
      onProgress: (p) => {
        console.log(`Progreso: ${p}%`);
      }
    });
  } catch (err) {
    console.error(err || err.message);
  }
})();
</script>

💡 Variables auxiliares y contexto compartido

Cuando usamos initCaptcha como módulo ES6 importado, cada llamada a la función se ejecuta en un entorno aislado y las variables internas no están accesibles externamente. Por eso, si necesitamos conservar información entre callbacks —como el texto original de un botón antes de cambiarlo— debemos definir una variable auxiliar en nuestro código, y usarla explícitamente en las funciones que pasamos en la configuración.

▶️ Ver demo

import { initCaptcha } from './captcha.js'; // o ruta correcta

// Definir memo en el contexto del módulo o script
let memo = null; // Variable auxiliar para onStart y onEnd

(async () => {
  try {
    await initCaptcha({
      // otros parámetros...
      onStart: () => {
        const btn = document.querySelector('#submitBtn');
        memo = btn.textContent;  // guardamos texto original en memo
        btn.disabled = true;
        btn.style.cursor = 'wait';
        btn.textContent = 'Por favor espere...';
      },
      onEnd: () => {
        const btn = document.querySelector('#submitBtn');
        btn.textContent = memo;  // restauramos texto original desde memo
        btn.disabled = false;
        btn.style.cursor = 'pointer';
      }
    });
  } catch (err) {
    console.error(err || err.message);
  }
})();

🔄 Ejemplo: autoFormIntegration con múltiples formularios

▶️ Ver demo

<script type="module">
  import { initCaptcha } from './path/to/captcha.js';

  // Array de selectores de formularios que usarán BladeCAPTCHA
  const forms = ['#contactForm', '#loginForm', '#newsletterForm'];

  forms.forEach(async (formSelector) => {
    // Variable auxiliar para guardar el texto original del botón submit
    let memo;

    try {
      await initCaptcha({
        mode: 'autoFormIntegration',
        apiBaseUrl : '../php',
        formSelector: formSelector,
        inputName: 'captcha_token',
        submitButtonSelector: `${formSelector} button[type="submit"]`,
        onStart: () => {
          const btn = document.querySelector(`${formSelector} button[type="submit"]`);
          if (!btn) return;
          memo = btn.textContent;
          btn.textContent = 'Verificando...';
          btn.disabled = true;
          btn.style.cursor = 'wait';
        },
        onEnd: () => {
          const btn = document.querySelector(`${formSelector} button[type="submit"]`);
          if (!btn) return;
          btn.textContent = memo || 'Enviar';
          btn.disabled = false;
          btn.style.cursor = 'pointer';
        }
      });
    } catch (err) {
      console.error(err || err.message);
    }
  });
</script>

🔧 Características técnicas destacadas

🎨 Personalizable

Podés ajustar su apariencia, visibilidad y comportamiento fácilmente.

📱 Ejecución adaptativa

La ejecución del desafío se ajusta localmente según la capacidad del dispositivo.

❄️ Liviano

Solo 9 KB de JavaScript minimizado, incluyendo los workers.

🤖 Difícil para bots

Algoritmos diseñados para resistir ataques automáticos.

💸 Gratis

100% libre de costos, sin planes premium.

📜 Licencia MIT

Podés usarlo en proyectos personales o comerciales sin restricciones.

🎯 Ajuste de la ejecución

BladeCAPTCHA optimiza dinámicamente los parámetros de ejecución de su proof-of-work (como el número de workers en paralelo o el tamaño de los lotes de cálculo) en función de las capacidades del hardware del dispositivo.

Esta optimización garantiza que dispositivos con menor potencia de procesamiento, como los móviles, completen la verificación con agilidad, mientras se mantiene un alto nivel de seguridad contra bots.

Todo el proceso de cálculo y ajuste se realiza localmente en el navegador. En ningún caso se envía información del hardware o métricas de rendimiento a servidores externos, garantizando así la plena privacidad del usuario.

📁 Archivo de configuración (PHP)

BladeCAPTCHA permite definir algunos parámetros del comportamiento general desde un archivo PHP simple.

Este archivo define constantes y variables de configuración que son utilizadas por la biblioteca durante la validación del token. Por razones de seguridad es conveniente que sea ubicado fuera del directorio público (por ejemplo, en BladeCAPTCHA/config/config.php).

Ejemplo:

<?php
// BladeCAPTCHA/config/config.php
define('CAPTCHA_SECRET_KEY', '3b7e151628aed2a6abf7158809cf4f3c');
define('CAPTCHA_DIFFICULTY', 4);
define('CAPTCHA_EXPIRY', 300); // 5 minutos

// Configuración CORS para peticiones AJAX desde frontend
$config['cors_enabled'] = true; // Activar/desactivar manejo CORS
$config['cors_allowed_origins'] = [ // Orígenes permitidos para CORS (no usar '*')
    'http://localhost',
    'http://127.0.0.1',
    'http://[::1]'
];
$config['cors_allow_credentials'] = true; // Permitir cookies y credenciales CORS

Parámetros disponibles:

Si no se define CAPTCHA_SECRET_KEY, no se podrá validar ningún token.

🛡️ Verificación en el servidor (PHP)

Después de que el navegador complete el desafío, el token generado se envía junto con el formulario. Tu backend debe verificar ese token para asegurarse de que es válido y reciente.

Paso 1: incluir la librería

require_once __DIR__ . '/captcha-lib.php';
use function Captcha\validateToken;

Paso 2: validar el token recibido

Este ejemplo verifica el token CAPTCHA y, si es válido, continúa con el procesamiento del formulario.

<?php
// BladeCAPTCHA/public/php/procesar-formulario.php
require_once __DIR__ . '/captcha-lib.php';
use function Captcha\validateToken;

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    http_response_code(405);
    exit('Método no permitido');
}

// Sanitiza y valida el token del captcha (entrada siempre como string)
$token = trim((string)($_POST['captcha_token'] ?? ''));

function render($msg) {
    exit("<!DOCTYPE html><meta charset='UTF-8'><body>$msg</body>");
}

// Validar formato y autenticidad
if (!preg_match('/^[a-f0-9]{32}$/i', $token) || !validateToken($token)) {
    render(' CAPTCHA inválido.');
}

if (!isset($_POST['nombre'])) {
    render(' Falta el campo "nombre".');
}

//  CAPTCHA válido
echo "<!DOCTYPE html><meta charset='UTF-8'><body><h1>CAPTCHA correcto</h1><pre>";
foreach ($_POST as $k => $v) {
    printf("<strong>%s:</strong> %s\n",
        htmlspecialchars($k, ENT_QUOTES, 'UTF-8'),
        htmlspecialchars((string)$v, ENT_QUOTES, 'UTF-8')
    );
}
echo "</pre></body>";

Buenas prácticas

  • Validá siempre el token en el servidor.
  • Si usás manualHandling junto con manualHandlingAutoStartOnLoad: true, evaluá cuidadosamente si tiene sentido incluir también verifyButtonSelector. Aunque el arranque automático y el botón de verificación pueden coexistir, en la práctica suele ser redundante desde el punto de vista de la usabilidad.
  • Tené en cuenta que el token tiene vencimiento breve (~60 segundos) y es de un solo uso.
  • Asegurate de que entre cada desafío iniciado por el mismo usuario exista un intervalo mínimo de 10 segundos antes de comenzar otro, para evitar que el servidor rechace la solicitud.

Preguntas frecuentes

🧵 ¿Qué pasa si el navegador no soporta Web Workers?

BladeCAPTCHA necesita Web Workers para ejecutar el desafío de proof-of-work sin bloquear la interfaz. Si el navegador no los soporta, no podrá completarse la validación y el formulario será rechazado. Actualmente no hay un modo alternativo integrado.

🚫 ¿Y si el usuario desactiva JavaScript?

El sistema depende de JavaScript para generar y resolver el desafío. Con JavaScript deshabilitado no se podrá generar el token y el envío será bloqueado.

🌐 ¿BladeCAPTCHA funciona sin conexión a Internet?

Sí, al ser auto-hosteado y no depender de servicios externos, BladeCAPTCHA puede funcionar incluso en entornos sin conexión a Internet, siempre que el servidor local esté disponible.

¿Cuánto dura un token?

Cada token es válido por aproximadamente 60 segundos y solo puede usarse una vez.

📝 ¿Puedo usarlo en múltiples formularios?

Sí, podés proteger varios formularios en la misma página. Pero debe mediar un intervalo mínimo de 10 segundos entre un desafío y el siguiente.

🔄 ¿Qué sucede si el usuario cierra o recarga la página durante un desafío?

Si se recarga o cierra la página antes de completar el desafío, el token generado se pierde y deberá generarse uno nuevo al intentar enviar el formulario nuevamente.

🎨 ¿Se puede personalizar la apariencia del CAPTCHA?

Sí, BladeCAPTCHA es completamente personalizable mediante CSS y opciones de configuración, lo que permite adaptar su apariencia y comportamiento a las necesidades de tu sitio.

⚙️ ¿Cómo ajustar la dificultad del desafío?

Para ajustar la dificultad del captcha, modificá el valor CAPTCHA_DIFFICULTY en config/config.php.

🔒 ¿El sistema almacena datos personales?

No. BladeCAPTCHA no guarda, rastrea ni envía información personal a terceros.

🖼️ ¿Por qué BladeCAPTCHA no utiliza imágenes o desafíos visuales?

Para garantizar accesibilidad y privacidad, BladeCAPTCHA evita el uso de imágenes, sonidos o videos. En cambio, se basa en un desafío computacional (proof-of-work) que es transparente para el usuario y compatible con tecnologías asistivas.

☁️ ¿Por qué no usa CDN o servicios externos?

BladeCAPTCHA evita cargar scripts desde CDN o servicios externos para no depender de terceros que puedan recopilar datos, usar cookies o afectar la privacidad y seguridad de los usuarios.

🛠️ ¿Puedo integrarlo sin validación en backend?

No. BladeCAPTCHA requiere que tu backend valide el token recibido, para garantizar que el desafío fue resuelto correctamente y dentro del tiempo permitido.

🌍 ¿Funciona si frontend y backend se alojan en diferentes servidores o dominios?

Sí, es posible usar el módulo JavaScript de BladeCAPTCHA aunque el frontend y backend estén en diferentes servidores o dominios. Para que las llamadas AJAX (fetch) funcionen correctamente en este tipo de entornos cross-origin, BladeCAPTCHA incluye una configuración integrada para manejar CORS (Cross-Origin Resource Sharing).

Esto significa que el backend PHP puede enviar automáticamente las cabeceras HTTP necesarias (Access-Control-Allow-Origin, Access-Control-Allow-Credentials, etc.) según la configuración definida en config.php ($config['cors_enabled'], $config['cors_allowed_origins'], etc.).

Sin embargo, es responsabilidad del desarrollador definir correctamente los orígenes permitidos en cors_allowed_origins para mantener la seguridad, y ajustar otras opciones de CORS según sus necesidades.

Si usás BladeCAPTCHA en el mismo dominio o subdominio que el backend, normalmente no es necesario hacer ninguna configuración adicional.

🔑 ¿Cómo obtener el token generado por BladeCAPTCHA en mi código?

Depende del modo de integración configurado:

  • manualHandling : el token se recibe como argumento del callback onSuccess(token) al completarse el desafío.
  • autoFormIntegration : el token se inserta automáticamente en un campo oculto cuyo nombre es el valor configurado en inputName, dentro del formulario protegido.

En ambos casos, este token debe enviarse al backend para validarlo con la función validateToken().

🐍 ¿Se puede usar BladeCAPTCHA sin PHP?

Actualmente BladeCAPTCHA funciona con PHP, pero pronto estará disponible soporte para backend en Python y otros lenguajes.

🤖 ¿Por qué se llama BladeCAPTCHA y qué representa el logo?

El nombre rinde homenaje a Blade Runner, donde el test Voight-Kampff (que inspira nuestro logo de tortuga invertida) funciona como un CAPTCHA biológico para distinguir humanos de replicantes. Esta referencia filosófica -que bebe de los arquetipos de Carl Jung que influyeron a Philip K. Dick- refleja nuestra aproximación: un desafío que evalúa no solo respuestas, sino cómo se generan.

📊 Comparativa con otros sistemas CAPTCHA

Característica BladeCAPTCHA reCAPTCHA hCaptcha
Cumplimiento RGPD/CCPA No Parcial
Privacidad No recopila datos personales Envía datos a Google Envía datos a terceros
Dependencia de servicios externos No, se ejecuta totalmente en tu servidor sin CDN ni APIs externas Sí, carga scripts y depende de APIs externas de Google Sí, depende de CDN y APIs de terceros
Accesibilidad Alta, sin desafíos visuales Baja Media
Personalización Completa vía CSS/HTML Nula Limitada
Doble modo de integración Flexible: automático o manual Solo automático Solo automático
Rendimiento Uso de Web Workers para evitar bloqueos No documentado / no confirmado Sin Web Workers
Auto-hosteado Sí, corre totalmente en tu servidor No No
Código abierto 100% Open Source y sin APIs externas Propietario, depende de Google Propietario, depende de terceros
Licencia MIT Propietaria Propietaria
Costo Gratis Gratis con límites Gratis / Pago
Créditos obligatorios No Sí (en plan gratuito)

Apoyá el proyecto

BladeCAPTCHA es un proyecto 100% libre y autofinanciado. Si te resultó útil y querés ayudar a cubrir costos de hosting, mejoras y soporte, podés invitarnos un cafecito . ¡Toda colaboración es bienvenida!

Invitar un cafecito