En este artículo, aprenderemos cómo hacer la comprobación de los CAPTCHA en el lado servidor (backend) mediante PHP.
La palabra CAPTCHA es un acrónimo que significa "Prueba de Turing Pública Completamente Automatizada para diferenciar Ordenadores de Humanos" en inglés.
El término "Turing" se refiere a Alan Turing, un matemático y criptógrafo británico que es ampliamente considerado como el padre de la informática y la inteligencia artificial. Turing es conocido por su trabajo en la descodificación de los códigos de la máquina Enigma utilizada por los nazis durante la Segunda Guerra Mundial, así como por su propuesta del test de Turing, una prueba que se utiliza para evaluar la capacidad de una máquina para exhibir un comportamiento indistinguible al de un ser humano.
Los creadores de CAPTCHA (Luis von Ahn, Manuel Blum y Nicholas J. Hopper) se inspiraron en el test de Turing y desarrollaron un sistema que involucraba la generación de un desafío visual o auditivo que un ser humano podría resolver fácilmente, pero que resultaría difícil para una máquina. Esto permitió a los sitios web protegerse contra el spam y la actividad maliciosa en línea, al tiempo que aseguraban que los usuarios eran seres humanos reales. Fundaron la empresa Captcha.net que se dio a conocer rápidamente. Yahoo compró Captcha. Era una época temprana de Internet pero ya tenía bastantes usuarios. En muchos foros y formularios se usaba el captcha de una palabra con las letras distorsionadas.
Luego, Luis von Ahn desarrolló una versión mejorada y creó la empresa reCaptcha para un nuevo proyecto. Ahora, el desafío mostraba dos palabras distorsionadas. Una de ellas era seleccionada al azar de un diccionario de unas 1000 palabras, y el Captcha conocía la respuesta. La otra palabra era una imagen digitalizada de un libro. Si el usuario resolvía correctamente la palabra conocida, se suponía que también había resuelto correctamente la segunda palabra desconocida. Esto permitía aprovechar el trabajo voluntario de esas personas para digitalizar libros. Google compró reCaptcha porque les gustó mucho esta idea y decidieron utilizarla en su proyecto de biblioteca digital https://books.google.es/.
La siguiente evolución del CAPTCHA consistía en presentar pruebas muy fáciles para los humanos, pero complicadas para los bots. Dichas pruebas podrían ser desde responder preguntas matemáticas sencillas como "¿cuánto es 4 + 3?" hasta seleccionar en una foto dividida en varias partes dónde se encuentran determinados elementos como semáforos o coches.
Actualmente, los CAPTCHA no requieren que los usuarios resuelvan pruebas. Resolver pruebas siempre ha sido algo tedioso para los usuarios y les hace perder tiempo. En su lugar, los CAPTCHA utilizan algoritmos internos que examinan datos del usuario, como la dirección IP o las cookies activas. También registran el movimiento del ratón hasta hacer clic en el botón de identificación, miden el tiempo que se tarda en rellenar el formulario y realizan otras comprobaciones que se mantienen en secreto para no dar pistas a los programadores de bots. A pesar de todo esto, si el CAPTCHA no está seguro de que eres humano, seguirá mostrándote pruebas.
Incluso esta modesta web, con un reducido número de visitas que debería pasar desapercibida en la inmensidad de internet, antes de implementar el captcha, recibía casi a diario mensajes con ofertas de bots variadas. Era raro el día en que no entraba un formulario de la página de contacto con propaganda de criptomonedas, posicionamiento web de SEOS sin escrúpulos, telepredicadores modernos que se hacen llamar "coachs" y demás fauna diversa.
Aunque Google y su sistema "reCAPTCHA" son actualmente los más conocidos, existen otras alternativas igual de efectivas. Una de ellas es el servicio gratuito de Cloudflare llamado "Turnstile".
Otra alternativa es hCaptcha, que cuenta con una versión gratuita con algunas limitaciones, lo que la hace perfecta para pequeños proyectos. Además, ofrecen una versión de pago más completa, enfocada en el uso empresarial.
La validación de un captcha se puede realizar tanto en el backend como en el frontend, dependiendo de los requisitos y la arquitectura de la aplicación. En este artículo veremos algunos ejemplos completos con el objetivo de que sean más comprensibles para programadores con menos experiencia.
Generalmente, se recomienda realizar la validación en el backend, ya que es más seguro y difícil de manipular para los usuarios malintencionados. La validación del captcha en el backend se puede hacer mediante lenguajes de programación como PHP, Python, Java, Ruby, etc.
Desde hace algún tiempo, parece que Google quiere que dejemos de utilizar los recaptchas clásicos V2 y V3 y nos demos de alta en el nuevo Recaptcha Enterprise. Tanto es así que encontrar la página para registrar los recaptchas clásicos desde los menús de Google me resultó imposible, y tuve que recurrir a una búsqueda en Google para acceder al apartado de administración de recaptchas. Además, durante el proceso de registro de un nuevo recaptcha clásico en el formulario, se nos muestra la opción de pasar al recaptcha enterprise. Todo indica que el recaptcha clásico será reemplazado en pocoo tiempo.
Para este ejemplo crearemos un sencillo formulario HTML con 3 campos que pida "nombre", "email" y "mensaje" y un botón de "Enviar". El lugar recomendado para colocar el widget de recaptcha es justo antes del botón "Enviar". Cuando el usuario envíe el formulario Google Recaptcha resolverá si es una persona o bot. No existen Captchas sin tasa de error y, precisamente, la V2, al ser algo antigua, puede ser engañada por bots actuales. Aún así, como ejercicio de aprendizaje, sigue siendo una práctica útil.
Para registrar un recaptcha iremos a la página https://www.google.com/recaptcha/admin. En principio, Recaptcha es gratis hasta 1.000.000 de consultas por mes. Solo necesitamos una cuenta de usuario de Google, la misma que se usa para servicios como Gmail o Drive. Para Recaptcha V2 y V3 no es necesario darse de alta en Google Cloud. Sin embargo, si deseamos implementar Recaptcha Enterprise, que es mucho más robusto y fiable para detectar bots, necesitamos registrarnos en Google Cloud. Aunque sea gratuito, nos pedirán una tarjeta de crédito para facturarnos si sobrepasamos los límites gratuitos, y a nivel de configuración es mucho más complejo.
Para crear un reCAPTCHA, simplemente tenemos que pulsar el botón '+' y aparecerá un formulario.
Lo primero que aparece en la parte superior es un recuadro que nos invita a cambiar al modo Enterprise debido a las ventajas que ofrece. Por el momento, lo ignoraremos.
Rellenaremos los siguientes campos:
Una vez que hayamos introducido los datos, se mostrarán las dos claves necesarias para trabajar con la API de reCAPTCHA: la clave web y la clave secreta. La clave secreta, como su propio nombre indica, nunca debe ser expuesta. Mientras que la clave web puede ser visible mirando el código fuente desde cualquier navegador, la clave secreta se encuentra en el backend y es inaccesible para los usuarios. En este ejemplo, me permito mostrarla únicamente con fines ilustrativos, pero será borrada al finalizar el ejemplo.
Ahora podemos implementar la API de reCAPTCHA en un formulario o en cualquier sitio donde los usuarios ingresen datos para descartar bots.
El renderizado automático es muy sencillo de implementar. Enseguida descubriremos cómo insertar un reCAPTCHA en una página web con tan solo 2 líneas de código.
Como ejemplo base usaremos el siguiente formulario:
<!DOCTYPE html>
<html>
<head>
<title>Formulario con reCAPTCHA</title>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head>
<body>
<h1>Formulario con reCAPTCHA V2</h1>
<form action="?" method="post">
<label for="nombre">Nombre:</label>
<input type="text" name="nombre" id="nombre" required><br><br>
<label for="email">Email:</label>
<input type="email" name="email" id="email" required><br><br>
<label for="mensaje">Mensaje:</label>
<textarea name="mensaje" id="mensaje" required></textarea><br><br>
<!-- clave sitio web -->
<div class="g-recaptcha" data-sitekey="6LdcSYAlAAAAACQ8EuuAzrN83k9Cp-DcjtbOldNW"></div>
<br>
<input type="submit" value="Enviar">
</form>
</body>
</html>
Se trata de un formulario sencillo que se enviará a la misma URL. El atributo action="?"
puede ser omitido en este caso porque tanto la vista HTML como la lógica PHP van en el mismo archivo.
Dentro de la sección <head>
, se debe incluir el script que carga la biblioteca de
reCAPTCHA de Google. La carga será asíncrona y la ejecución será diferida mediante los atributos
async
y defer
. El atributo async permite que la carga del archivo
api.js
se realice en segundo plano mientras continúa la carga del resto de elementos
de la página (DOM). El atributo defer garantiza que los scripts se ejecuten en el orden en que aparecen en el
código HTML.
<head>
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
</head>
Para mostrar el elemento "reCAPTCHA", en el elemento HTML que nos interese, en este caso entre etiquetas div
,
insertamos la clase de reCAPTCHA con el atributo class="g-recaptcha"
y la
clave de sitio web con el atributo data-sitekey="mi_clave_web"
.
Con esta única línea mostramos el CAPTCHA y además se genera el token que se evaluará en el servidor.
<div class="g-recaptcha" data-sitekey="6LdcSYAlAAAAACQ8EuuAzrN83k9Cp-DcjtbOldNW"></div>
Como puedes ver, con solo 2 líneas de código tenemos el reCAPTCHA listo para ser utilizado. Sin embargo, todavía falta implementar la parte donde se verifica si el usuario es humano o no. Puedes probar su funcionamiento subiendo el código a un VPS con tu dominio o en un entorno de desarrollo web local. No olvides visitar https://www.google.com/recaptcha/admin y solicitar las claves para tu dominio o localhost.
Con el depurador de tu navegador favorito, puedes examinar qué datos se envían en la solicitud POST
. La siguiente imagen muestra una captura de pantalla en Firefox.
Los parámetros se envían en pares de datos clave-valor. Tenemos 'nombre', 'email' y 'mensaje' que son los datos propios del formulario. Además, tenemos el parámetro 'g-recaptcha-response', que es la respuesta única en forma de token generada al resolver el reCAPTCHA. Este parámetro debe ser validado y verificado desde el lado del servidor.
El siguiente código PHP envía la respuesta a la API de reCAPTCHA de Google para verificar su autenticidad y determinar si el usuario es un humano o un bot.
<?php
// Comprobar que se ha enviado el formulario
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Comprobar que se ha proporcionado la clave secreta de reCAPTCHA
if (!empty($_POST['g-recaptcha-response'])) {
// Verificar la respuesta de reCAPTCHA con la clave secreta y la respuesta del usuario
$respuesta_recaptcha = $_POST['g-recaptcha-response'];
$clave_secreta_recaptcha = '6LdcSYAlAAAAAPRoOJNKj-Gaw8xef3VpeJdpogfB';
$respuesta = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret=' .
urlencode($clave_secreta_recaptcha) . '&response=' . urlencode($respuesta_recaptcha));
$respuesta = json_decode($respuesta);
//mostrar objeto en depuración
print2depurar($respuesta);
if ($respuesta->success) {
// La respuesta de reCAPTCHA es válida, procesar el envío del formulario
$nombre = $_POST['nombre'];
$email = $_POST['email'];
$mensaje = $_POST['mensaje'];
// Hacer lo que sea necesario con los datos del formulario
echo 'Formulario enviado correctamente';
} else {
// La respuesta de reCAPTCHA es inválida, mostrar un mensaje de error
echo 'Error: por favor, verifica que eres humano';
}
} else {
// La clave secreta de reCAPTCHA no se proporcionó, mostrar un mensaje de error
echo 'Error: por favor, verifica que eres humano. No marcaste la casilla "No soy un Robot"';
}
}
?>
Además, con fines didácticos, añado una función que imprime de diferentes maneras el objeto
$respuesta
. Ver los resultados durante el desarrollo ayuda mucho a detectar errores y a entender cómo trabaja el código.
<?php
// Diferentes maneras de mostrar un objeto PHP para depurar
function print2depurar($resp)
{
echo "# Con var_dump()" . "<br><br>";
var_dump($resp);
echo "<br><br>";
echo "# Con print_r() entre etiquetas HTML <pre>" . "<br>";
echo "<pre>";
print_r($resp);
echo "</pre>";
echo "# Con json_encode con parámetro JSON_PRETTY_PRINT y nl2br()" . "<br><br>";
$jsonRespuesta = json_encode($resp, JSON_PRETTY_PRINT);
//Convertir los saltos de línea (\n) en etiquetas HTML <br>.
echo nl2br($jsonRespuesta);
echo "<br><br>";
}
?>
Primero, se verifica si el formulario ha sido enviado utilizando la variable 'REQUEST_METHOD']
.
Si el método de solicitud es POST, significa que el formulario ha sido enviado y se procede a realizar la validación.
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Código de validación
}
A continuación, se verifica si se ha proporcionado la clave secreta de reCAPTCHA en el campo g-recaptcha-response. Esto es necesario para poder realizar la verificación con el servicio de reCAPTCHA.
if (!empty($_POST['g-recaptcha-response'])) {
// Código de verificación de reCAPTCHA
}
El valor de $_POST['g-recaptcha-response']
se asigna en una variable.
Se crea otra variable con la clave secreta proporcionada por Google.
$respuesta_recaptcha = $_POST['g-recaptcha-response'];
$clave_secreta_recaptcha = '6LdcSYAlAAAAAPRoOJNKj-Gaw8xef3VpeJdpogfB';
Con la función file_get_contents()
se hace una solicitud a la API de Google
reCAPTCHA, enviando la clave secreta y el valor g-recaptcha-response
. Antes de enviar los valores
en la URL de la solicitud, se utiliza la función urlencode()
para codificar
las cadenas de texto de manera segura. La respuesta de la API se guarda en una variable para verificar el
resultado y tomar las acciones correspondientes.
$respuesta = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret=' .
urlencode($clave_secreta_recaptcha) . '&response=' . urlencode($respuesta_recaptcha));
La respuesta obtenida se decodifica como un objeto JSON utilizando la función json_decode. Esto convierte la respuesta en un objeto PHP que se puede utilizar para acceder a sus propiedades.
$respuesta = json_decode($respuesta);
Durante la depuración se llama a una función para mostrar en pantalla la respuesta obtenida de reCAPTCHA.
//mostrar objeto en depuración
print2depurar($respuesta);
En una de las pruebas obtenemos esta respuesta:
# Con var_dump()
object(stdClass)#1 (3) { ["success"]=> bool(true) ["challenge_ts"]=> string(20) "2023-05-18T15:26:02Z" ["hostname"]=> string(20) "www.tallerdeapps.com" }
# Con print_r() entre etiquetas pre
stdClass Object
(
[success] => 1
[challenge_ts] => 2023-05-18T15:26:02Z
[hostname] => www.tallerdeapps.com
)
# Con json_encode con parámetro JSON_PRETTY_PRINT y nl2br()
{
"success": true,
"challenge_ts": "2023-05-18T15:26:02Z",
"hostname": "www.tallerdeapps.com"
}
Con cualquiera de las 3 funciones obtenemos los mismos valores pero con pequeñas diferencias en la forma de presentarlos en pantalla.
El objeto devuelto tiene 3 propiedades:
success
: Indica si la verificación de reCAPTCHA fue exitosa. Si tiene un valor true
, o 1
significa que la respuesta del usuario fue validada correctamente.challenge_ts
: Es la marca de tiempo (timestamp) en la que se generó el desafío reCAPTCHA. En este caso, la marca de tiempo es "2023-05-18T15:26:02Z".hostname
: Indica el hostname (nombre de dominio) del sitio web donde se realizó la verificación reCAPTCHA. En este caso, el hostname es "www.tallerdeapps.com".
Si la propiedad success
de la respuesta es true
significa que la validación de reCAPTCHA ha sido exitosa y se puede procesar el envío del formulario.
if ($respuesta->success) {
// Código para procesar el envío del formulario
} else {
// Código en caso de error de reCAPTCHA
}
Dentro del bloque if ($respuesta->success)
, se pueden obtener los datos del formulario (nombre, email, mensaje) desde
$_POST y asignarlos a variables para su posterior uso.
$nombre = $_POST['nombre'];
$email = $_POST['email'];
$mensaje = $_POST['mensaje'];
En el ejemplo, se muestra un mensaje indicando que el formulario ha sido enviado correctamente.
echo 'Formulario enviado correctamente';
Si la respuesta es negativa o no se realiza el check en el reCAPTCHA, la lógica de los condicionales mostrará el mensaje correspondiente.
Con esto, damos por finalizada la explicación de Google reCAPTCHA V2. Aunque esta versión sigue siendo funcional, se recomienda utilizar la versión 3, ya que tiene una tasa de filtrado superior.
El renderizado explícito de reCAPTCHA puede ser útil en situaciones específicas donde deseas tener un mayor control sobre el proceso de renderizado. Aunque el renderizado automático es más fácil de implementar, el renderizado explícito puede ofrecerte más flexibilidad y personalización.
Algunas razones por las que podría preferirse el renderizado explícito de reCAPTCHA son:
Control de eventos: Al realizar el renderizado explícito, puedes asociar eventos personalizados a diferentes acciones del widget, como la validación del formulario o la actualización de la página.
Diseño personalizado: El renderizado explícito te permite personalizar el aspecto visual del widget para que se adapte mejor al diseño y estilo de tu sitio web.
Control de carga: Puedes controlar exactamente cuándo y dónde se carga el widget reCAPTCHA en tu página, lo que puede ser útil si tienes requisitos de rendimiento o si necesitas cargarlo en un momento específico durante la navegación del usuario.
Si no necesitas estas funcionalidades adicionales, el renderizado automático puede ser la opción más sencilla y rápida de implementar.
Como ejemplos para respaldar las explicaciones, he tomado como referencia los que aparecen en la documentación oficial. Estos ejemplos de renderizado explícito están completos y funcionan sin necesidad de añadir nada adicional. No dudes en consultar la documentación oficial para obtener más información.
Simplificando lo máximo posible, el código mínimo para implementar un reCAPTCHA explícito puede ser el siguiente:
<!DOCTYPE html>
<html>
<head>
<title>reCAPTCHA demo: Render explícito mínimo</title>
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
<script type="text/javascript">
function onloadCallback() {
// Renderiza el elemento HTML con id 'ejemplo' como un widget recaptcha
grecaptcha.render('ejemplo', {
'sitekey': '6LeOxLokAAAAABbF3Fn_OulxRlOiM9NwRQB0UgAA',
});
};
</script>
</head>
<body>
<!-- Formulario mínimo -->
<form action="?" method="POST">
<div id="ejemplo"></div>
<br>
<input type="submit" value="Enviar">
</form>
</body>
</html>
En la URL de carga de la API pasamos 2 parámetros:
<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>
onload=onloadCallback
: Este parámetro indica que se debe llamar a la función onloadCallback()
una vez que la API se haya cargado correctamente. Esto permite realizar acciones adicionales una vez que la API esté lista para su uso.
render=explicit
: Este parámetro especifica que el renderizado del reCAPTCHA debe realizarse explícitamente. En lugar de que la API lo haga automáticamente, nosotros seremos responsables de invocar al método grecaptcha.render()
para renderizar el widget reCAPTCHA en el lugar deseado.
La carga de la API, opcionalmente, admite un tercer parámetro para seleccionar el idioma en el que se mostrará el widget reCAPTCHA. El parámetro se agrega a la URL como &hl=xx
, donde xx
es una abreviatura del idioma (por ejemplo, "ca" para catalán, "fr" para francés, etc.). Puedes encontrar la lista completa de idiomas soportados en la documentación. Por ejemplo, para mostrar un reCAPTCHA en catalán, utilizaríamos: ...onload=onloadCallback&render=explicit&hl=ca&hl=ca
.
Siguiendo la lectura del código, la función onloadCallback()
se invocará cuando se cargue correctamente la API de reCAPTCHA (por "...?onload=onloadCallback..."). Esta función se encargará de llamar al renderizador del widget reCAPTCHA, el método grecaptcha.render()
.
function onloadCallback() {
// Renderiza el elemento HTML con id 'ejemplo' como un widget recaptcha
grecaptcha.render('ejemplo', {
'sitekey': '6LeOxLokAAAAABbF3Fn_OulxRlOiM9NwRQB0UgAA',
});
};
El método grecaptcha.render
admite varios parámetros. Puedes encontrar la lista completa
aquí. (NOTA: Se
recomienda consultar los parámetros en inglés, ya que la traducción automática proporciona valores incorrectos).
El primer valor 'ejemplo'
es el contenedor donde irá el widget, se indica con el 'id' del elemento HTML
elegido.
El segundo parámetro es un objeto que contiene pares clave-valor, por ejemplo,
{'sitekey': 'tu_clave_web', 'theme': 'dark', 'size': 'compact'}
.
En este ejemplo sencillo, solo se incluye el par clave-valor con la clave 'sitekey'. Este dato es el único obligatorio para que el reCAPTCHA
funcione correctamente.
Si no se especifica el 'theme' (colores CSS) ni el 'size', los valores predeterminados serán 'light' y 'normal' respectivamente. Para un fondo oscuro, se debe usar el valor 'dark'. Para el tamaño, opcionalmente podemos asignar el valor 'compact', que es más cuadrado y compacto.
Dejando la parte de JavaScript a un lado, en la sección HTML ponemos el elemento contenedor del widget justo encima del botón "ENVIAR" del formulario. La validación en el backend es exactamente igual que en el caso anterior de renderizado automático. Se enviará un parámetro POST llamado 'g-recaptcha-response' que contiene el token generado por el reCAPTCHA.
<div id="ejemplo"></div>
Desde el backend, implementaremos el código necesario para capturar el POST 'g-recaptcha-response'. Podemos usar el mismo código del ejemplo anterior de reCAPTCHA v2 con renderizado automático y depurar con el navegador para verificar que el token es enviado. Siendo la V2 un poco obsoleta, no entraremos en más detalles ni desarrollaremos un ejemplo totalmente funcional. El objetivo de este capítulo se limita a entender cómo mostrar un desafío de manera programática.
Mientras que la versión 2 verifica las solicitudes con un reto, la versión 3 las evalúa mediante una puntuación. La API de Google asigna una puntuación decimal entre 0 y 1 a cada solicitud. Valores superiores a 0.6 indican una alta probabilidad de que sea un humano, mientras que valores cercanos a 0 indican casi con certeza que se trata de un robot. La tasa de detección de bots en la versión 3 es claramente superior a la 2.
El proceso de registro para V3 es idéntico al que se muestra en los ejemplos anteriores. Desde la misma dirección en la que creamos el reCAPTCHA anterior, https://www.google.com/recaptcha/admin, hacemos clic en el botón '+'.
En el formulario, esta vez seleccionamos la opción reCAPTCHA V3
y finalmente le asignamos un dominio.
Al enviar el formulario, se generan nuevas claves de sitio web y una clave secreta válidas para V3, las cuales vamos a utilizar con el mismo formulario visto anteriormente, pero con las modificaciones necesarias.
La manera más simple de implementar el reCAPTCHA es vinculándolo a un botón:
<!DOCTYPE html>
<html>
<head>
<title>Formulario con reCAPTCHA</title>
<script src="https://www.google.com/recaptcha/api.js" data-badge="bottomright" ></script>
</head>
<body>
<h1>Formulario con reCAPTCHA V3</h1>
<form id="miForm" action="?" method="post">
<label for="nombre">Nombre:</label>
<input type="text" name="nombre" id="nombre" required><br><br>
<label for="email">Email:</label>
<input type="email" name="email" id="email" required><br><br>
<label for="mensaje">Mensaje:</label>
<textarea name="mensaje" id="mensaje" required></textarea><br><br>
<br>
<button class="g-recaptcha"
data-sitekey="6LezJyImAAAAAMY_aVlRAY1apDz7_seiwQPycg1_"
data-callback='onSubmit'
data-theme="dark"
data-action='formulario_ejemplo'>Enviar</button>
</form>
<script>
function onSubmit(token) {
document.getElementById("miForm").submit();
}
</script>
</body>
</html>
En reCAPTCHA V3, no se muestra un desafío visible al usuario y no se requiere la inserción de elementos visibles adicionales en el formulario. El desafío se realiza de forma invisible en segundo plano y se evalúa la interacción del usuario en el sitio web para determinar si es un bot o un usuario legítimo.
En esta versión, encontramos las siguientes diferencias con respecto a V2:
La carga de la API es de manera síncrona sin la necesidad de los atributos async o defer. En reCAPTCHA v2 necesita la carga asíncrona o diferida para no bloquear la carga de la página, reCAPTCHA v3 no requiere esos atributos debido a su naturaleza de análisis en segundo plano sin la necesidad de un widget interactivo.
<script src="https://www.google.com/recaptcha/api.js" data-badge="bottomright" ></script>
Opcionalmente, se puede agregar el atributo data-badge="bottomright"
para que aparezca un ícono en la esquina inferior derecha de la pantalla, como una indicación de que el sitio está protegido por reCAPTCHA. Si se omite, reCAPTCHA seguirá funcionando de la misma manera, pero no se mostrará ningún ícono que indique su implementación.
Como el reCAPTCHA está oculto vinculado al botón del formulario, no es necesario reservar espacio con elementos HTML para mostrar nada. Con este sistema, el reCAPTCHA actúa de manera invisible desde el propio botón al añadirle una serie de atributos.
<button class="g-recaptcha"
data-sitekey="6LezJyImAAAAAMY_aVlRAY1apDz7_seiwQPycg1_"
data-callback='onSubmit'
data-theme="dark"
data-action='formulario_ejemplo'>Enviar</button>
Cuando se usa este botón para enviar un formulario, el parámetro POST 'g-recaptcha-response' contiene el token de respuesta.
Al botón se le asignan varios atributos. El primero es de tipo 'clase' y los siguientes son del tipo 'dato'. Explicación línea a línea
<button> class="g-recaptcha"
: A la etiqueta HTML que representa un botón se le asigna la clase g-recaptcha
.data-sitekey="6LezJyImAAAAAMY_aVlRAY1apDz7_seiwQPycg1_"
: Especifica la clave del sitio (site key) de reCAPTCHA.data-callback='onSubmit'
: Especifica una función de devolución de llamada (callback function) que se llamará cuando se complete con éxito la verificación de reCAPTCHA.data-theme='dark'
: Especifica el tema (theme) de reCAPTCHA. Valores posibles: 'dark' o 'light'. Valor por defecto si se omite: 'light'.data-action='formulario_ejemplo'
: Especifica una etiqueta que servirá para identificar o localizar en qué sitio se usó este reCAPTCHA. Este dato es muy útil para supervisar el uso específico de este reCAPTCHA y también puede ser tenido en cuenta en la lógica de evaluación.Hasta aquí, ya podemos enviar al servidor el formulario y el token de reCAPTCHA. Podemos capturar el envío de tipo POST para depuración desde el navegador o con una gran variedad de técnicas utilizando PHP, JavaScript, etc. Sea cual sea el método, lo importante es verificar que se envíe el dato 'g-recaptcha-response' en la solicitud POST.
Aquí tenemos los nombres de las variables enviadas en el método POST de la solicitud HTTP tal como podemos verlos en el depurador del navegador:
En el depurador del navegador podemos verificar como el método POST de la solicitud HTTP envía cada uno de los campos del formulario y el token de reCAPTCHA.
[nombre] => Pedro
[email] => demo@tallerdeapps.com
[mensaje] => mi mensaje es este
[g-recaptcha-response] => 03AL8dmw9D1oSWGvyM24oe7rN2GyUJXRyJhJTK7AKWX1blNLf2Z_ccykvtz6Ydxs...
El token de reCAPTCHA se procesa con la API y nos devolverá varios datos, siendo el más importante la puntuación (score). La lógica en PHP es similar a la utilizada durante todo este artículo con el método V2. Solamente es necesario adaptar los condicionales para evaluar la puntuación.
<?php
// Comprobar que se ha enviado el formulario
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Comprobar que se ha proporcionado la clave secreta de reCAPTCHA
if (!empty($_POST['g-recaptcha-response'])) {
// Verificar la respuesta de reCAPTCHA con la clave secreta y la respuesta del usuario
$respuesta_recaptcha = $_POST['g-recaptcha-response'];
$clave_secreta_recaptcha = '6LezJyImAAAAAHOMtWmJcYsy-xGSKygLaTpvnZ_w';
$respuesta = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret=' .
urlencode($clave_secreta_recaptcha) . '&response=' . urlencode($respuesta_recaptcha));
$respuesta = json_decode($respuesta);
//mostrar objeto JSON con el resultado. Sólo en depuración.
echo "# Respuesta de la API reCAPTCHA V3" . "<br>";
echo "<pre>";
print_r($respuesta);
echo "</pre>";
// Verificar si el reCAPTCHA es correcto
if ($respuesta->success && $respuesta->score > .6 ) {
// Aquí vendría el procesamiento adicional de los datos al superar el reCAPTCHA
// ...
echo "¡El formulario se ha enviado correctamente!";
} else {
// El reCAPTCHA no fue válido, mostrar un mensaje de error
echo "Error: reCAPTCHA inválido. Por favor, inténtalo de nuevo.";
}
}
}
?>
El objeto JSON devuelto por la API tiene estos datos:
[success] => 1 // Valor 1 si esta solicitud era un token reCAPTCHA válido para su sitio
[challenge_ts] => 2023-06-02T20:50:33Z // fecha del envío
[hostname] => tallerdeapps.com // el dominio donde el reCAPTCHA se ha resuelto
[score] => 0.9 // puntuación entre 0.0 y 1.0
[action] => submit // el nombre de la acción para esta solicitud (según Google, es importante para verificar)
Al estar basada la V3 en una puntuación, la única parte a comentar del código que sea diferente a la V2 es el condicional para validación:
// Verificar si el reCAPTCHA es correcto
if ($respuesta->success && $respuesta->score > .6 ) {
// Aquí vendría el procesamiento adicional de los datos al superar el reCAPTCHA
// ...
echo "¡El formulario se ha enviado correctamente!";
} else {
// El reCAPTCHA no fue válido, mostrar un mensaje de error
echo "Error: reCAPTCHA inválido. Por favor, inténtalo de nuevo.";
}
Google recomienda una puntuación de corte superior a 0.5 para superar el reCAPTCHA. En todas mis pruebas al enviar el formulario, obtuve un valor de 0.9, por lo que creo que podemos aumentarlo a 0.6 sin problema.
Para demostrar el método de render programático tenemos este código:
<!DOCTYPE html>
<html>
<head>
<title>Formulario con reCAPTCHA</title>
<script src="https://www.google.com/recaptcha/api.js?render=6LezJyImAAAAAMY_aVlRAY1apDz7_seiwQPycg1_"
data-badge="bottomright"></script>
</head>
<body>
<h1>Formulario con reCAPTCHA V3</h1>
<form id="miForm" action="?" method="post">
<label for="nombre">Nombre:</label>
<input type="text" name="nombre" id="nombre" required><br><br>
<label for="email">Email:</label>
<input type="email" name="email" id="email" required><br><br>
<label for="mensaje">Mensaje:</label>
<textarea name="mensaje" id="mensaje" required></textarea><br><br>
<!-- Agrega aquí el campo oculto para el token de reCAPTCHA -->
<input type="hidden" name="token" id="recaptchaToken">
<br>
<input type="submit" value="Enviar" onclick="enviarToken(event)">
</form>
<script>
function enviarToken(event) {
event.preventDefault();
grecaptcha.ready(function() {
grecaptcha.execute('6LezJyImAAAAAMY_aVlRAY1apDz7_seiwQPycg1_', { action: 'submit' }).then(function(token) {
// Lógica para enviar el token al servidor backend
document.getElementById("recaptchaToken").value = token;
document.getElementById("miForm").submit();
});
});
}
</script>
</body>
</html>
Dentro del formulario, se incluye un campo oculto al que, mediante programación en JavaScript, le asignamos el valor del reCAPTCHA al momento de realizar el envío.
<!-- Agrega aquí el campo oculto para el token de reCAPTCHA -->
<input type="hidden" name="token" id="recaptchaToken">
La función JavaScript onEnviar(event)
utiliza la API de reCAPTCHA para generar y capturar un token reCAPTCHA V3 y luego asignarlo a un elemento
en el formulario con el ID "recaptchaToken". Aquí está la explicación línea por línea:
function onEnviar(event) {
: La función onEnviar se define y toma un parámetro llamado 'event'. Este parámetro representa el evento que se dispara cuando ocurre el envío del formulario.
event.preventDefault();
: Se encarga de prevenir el comportamiento predeterminado del evento de envío. En este caso, evita que el formulario se envíe y recargue la página.
grecaptcha.ready(function() { ... });
: Esto indica que se está esperando a que la API
de reCAPTCHA esté lista y cargada antes de ejecutar el código interno.
grecaptcha.execute('6LezJyImAAAAAMY_aVlRAY1apDz7_seiwQPycg1_', { action: 'formulario' }).then(function(token) { ... });
:
Aquí se llama a la función execute
de la API de reCAPTCHA. Toma dos argumentos: el
primer argumento es la clave del sitio reCAPTCHA y el segundo argumento es un objeto con la propiedad
action
establecida con la etiqueta 'formulario', que especifica la acción que se
está realizando en el formulario. Esta etiqueta no tiene está relacionado con los atributos id
o name
del form
. Se utiliza para distinguir diferentes
acciones o interacciones en un mismo formulario cuando se realiza la validación reCAPTCHA.
.then(function(token) { ... });
: Esto establece una función de devolución de llamada
que se ejecutará cuando se haya generado el token reCAPTCHA. El token generado se pasa como argumento a esta función y se
le asigna el nombre "token".
document.getElementById("recaptchaToken").value = token;
: Aquí se selecciona el elemento del formulario con el ID "recaptchaToken" y se le asigna el valor del token generado. Este elemento es un campo oculto en el formulario, y el token se asigna a su valor para enviarlo junto con otros datos cuando se envíe el formulario.
En resumen, este función genera un token reCAPTCHA V3 y lo asigna a un campo oculto en un formulario con el ID "recaptchaToken", para que pueda ser enviado junto con otros datos cuando se envíe el formulario y se realice la validación del reCAPTCHA del lado del servidor.
execute
cuando el usuario realice la acción en lugar de hacerlo cuando se cargue la página.
Esto es todo por la parte del frontend en modo renderizado programático. En el backend usaremos el mismo código en PHP del anterior con V3 y renderizado automático. El único cambio realizado es el nombre del dato POST enviado por HTTP con el token, que pasa de llamarse "g-recaptcha-response" a "token".
<?php
// Comprobar que se ha enviado el formulario
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Comprobar que se ha proporcionado la clave secreta de reCAPTCHA
if (!empty($_POST['token'])) {
// Verificar la respuesta de reCAPTCHA con la clave secreta y la respuesta del usuario
$respuesta_recaptcha = $_POST['token'];
$clave_secreta_recaptcha = '6LezJyImAAAAAHOMtWmJcYsy-xGSKygLaTpvnZ_w';
$respuesta = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret=' .
urlencode($clave_secreta_recaptcha) . '&response=' . urlencode($respuesta_recaptcha));
$respuesta = json_decode($respuesta);
//mostrar objeto JSON con el resultado. Sólo en depuración.
echo "# Respuesta de la API reCAPTCHA V3" . "<br>";
echo "<pre>";
print_r($respuesta);
echo "</pre>";
// Verificar si el reCAPTCHA es correcto
if ($respuesta->success && $respuesta->score > .6 ) {
// Aquí vendría el procesamiento adicional de los datos al superar el reCAPTCHA
// ...
echo "¡El formulario se ha enviado correctamente!";
} else {
// El reCAPTCHA no fue válido, mostrar un mensaje de error
echo "Error: reCAPTCHA inválido. Por favor, inténtalo de nuevo.";
}
}
}
?>
La versión Enterprise es la indicada para proyectos grandes. Encontrar las claves siguiendo su documentación no me ha parecido sencillo. Además, la forma de obtener las claves varía dependiendo de si se trata de una aplicación alojada en un servidor web de terceros o en la infraestructura propia de Google Cloud Platform. Para complicar aún más las cosas, si el proyecto está alojado fuera de Google Cloud, también existen diferencias en cuanto a si se permite Open Authorization (OAuth) y cuentas de servicio o no.
Aquí solo veremos cómo implementar reCAPTCHA Enterprise en un servidor ajeno a Google sin OAuth de la manera más sencilla que he encontrado. Aunque en https://www.google.com/recaptcha/admin aparece una opción para hacerlo, no es todo tan automático y necesariamente tenemos que darnos de alta en Google Cloud, crear un nuevo proyecto y habilitar la API reCAPTCHA Enterprise, para luego regresar al menú de administración de reCAPTCHA. Para evitar confusiones entre diferentes paneles, he preferido que todo el proceso se realice desde la consola de Google Cloud.
En principio, registrarse en Google Cloud es gratis, pero debemos tener cuidado de no habilitar otros servicios que tienen un coste. El número de validaciones necesarias para que Google solicite una cuenta de facturación es extremadamente alto para la gran mayoría de sitios web. Actualmente, se requieren 1 millón al mes.
Para ir a la página de inicio de Google Cloud, haz clic aquí. Después del proceso, accedemos a la "Consola", que es el menú inicial del Panel de Control. Debemos buscar un botón que diga "Crear un proyecto":
Aquí hay poco que comentar. Le damos un nombre al proyecto y, de manera opcional por comodidad, podemos cambiar el ID a algo más fácil de recordar.
Siguiendo en el menú contextual, seleccionamos "reCAPTCHA Enterprise":
Inicialmente, la API reCAPTCHA no está habilitada para los proyectos. Debemos activarla.
Pulsamos en "Crear clave".
En el formulario rellenamos los campos mínimos obligatorios: etiqueta del reCAPTCHA, tipo de plataforma "Sitio web" y el dominio a registrar.
Al hacer clic en "Crear Clave", se mostrará la clave pública web, que también se conoce como ID de clave. Para obtener la clave privada, iremos a "Descripción General" y luego a "Detalles" en la nueva pantalla.
La clave privada se encuentra al final y ahora se denomina "Clave secreta de reCAPTCHA heredada".
Estas dos claves son válidas únicamente para ser implementadas en un alojamiento web ajeno a Google Cloud. El código HTML para implementarlo es muy similar al que hemos estado viendo en todos los ejemplos anteriores.
Siguiendo la línea de todo el artículo, usamos el mismo formulario para apreciar mejor las diferencias. El siguiente ejemplo implementa un reCAPTCHA sin desafío (basado en puntuaciones, equivalente al V3 clásico). El reCAPTCHA va agregado a un botón.
<!DOCTYPE html>
<html>
<head>
<title>Formulario con reCAPTCHA</title>
<script src="https://www.google.com/recaptcha/enterprise.js?render=6LeeMoomAAAAAHMKm0AlFmALlWkSnHy01VQTy0J0"
data-badge="bottomright"></script>
</head>
<body>
<h1>Formulario con reCAPTCHA Enterprise</h1>
<form id="miForm" action="?" method="post">
<label for="nombre">Nombre:</label>
<input type="text" name="nombre" id="nombre" required><br><br>
<label for="email">Email:</label>
<input type="email" name="email" id="email" required><br><br>
<label for="mensaje">Mensaje:</label>
<textarea name="mensaje" id="mensaje" required></textarea><br><br>
<br>
<button class="g-recaptcha"
data-sitekey="6LeeMoomAAAAAHMKm0AlFmALlWkSnHy01VQTy0J0"
data-callback='onSubmit'
data-theme="dark"
data-action='opinion'>Enviar</button>
</form>
<script>
function onSubmit(token) {
document.getElementById("miForm").submit();
}
</script>
</body>
</html>
La carga del script de la API es similar a otros casos vistos, con la diferencia de que se trata de otro archivo. Además, enviamos la clave pública como parámetro. De manera opcional, podemos utilizar el atributo data-badge="bottomright"
para mostrar un icono de reCAPTCHA en una esquina de la pantalla.
<script src="https://www.google.com/recaptcha/enterprise.js?render=6LeeMoomAAAAAHMKm0AlFmALlWkSnHy01VQTy0J0"
data-badge="bottomright"></script>
La implementación en el botón se hace de manera idéntica al tipo V3 clásico
visto anteriormente. A la etiqueta HTML <button>
le damos el atributo de clase y
datos propios de reCAPTCHA.
La función onSubmit
es responsable de enviar el formulario al pulsar el botón. Siguiendo el ejemplo de la documentación oficial, recibe el parámetro `token`, pero no realiza ninguna acción directa con el token en este ejemplo específico. En otras situaciones, el token puede ser utilizado para realizar acciones adicionales o personalizadas según las necesidades del desarrollador.
El formulario envía los datos del propio formulario y, al implementar la API, el botón añade el token en el campo `g-recaptcha-response`. Esto se puede verificar fácilmente a través del depurador del navegador, como se ha mostrado a lo largo del artículo.
La lógica de verificación es la misma que vimos anteriormente para V3 clásico sin desafío basado en puntuación. Sobre el tema del 'score' hay que puntualizar que si bien los valores posibles son del rango 0.0 a 1.0, en los reCAPTCHA gratuitos las puntuaciones obtenidas pueden ser 0.1, 0.3, 0.7 y 0.9.
Siguiendo la recomendación de Google, tenemos en cuenta el nombre del campo "action" para la evaluación. Ahora, en la condición, se deben cumplir los requisitos de "success", "action" y "score".
<?php
// Comprobar que se ha enviado el formulario
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Comprobar que se ha proporcionado la clave secreta de reCAPTCHA
if (!empty($_POST['g-recaptcha-response'])) {
// Verificar la respuesta de reCAPTCHA con la clave secreta y la respuesta del usuario
$respuesta_recaptcha = $_POST['g-recaptcha-response'];
$clave_secreta_recaptcha = '6LeeMoomAAAAAE5GPNfj-rF28G9KFyRqkImP-RcJ';
$respuesta = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret=' .
urlencode($clave_secreta_recaptcha) . '&response=' . urlencode($respuesta_recaptcha));
$respuesta = json_decode($respuesta);
//mostrar objeto JSON con el resultado. Sólo en depuración.
echo "# Respuesta de la API reCAPTCHA Enterprise" . "<br>";
echo "<br>";
print_r($respuesta);
echo "<br>";
// Verificar si el reCAPTCHA es correcto
if ($respuesta->success && $respuesta->action == "opinion" && $respuesta->score > .6) {
// Aquí vendría el procesamiento adicional de los datos al superar el reCAPTCHA
// ...
echo "¡El formulario se ha enviado correctamente!";
} else {
// El reCAPTCHA no fue válido, mostrar un mensaje de error
echo "Error: reCAPTCHA inválido. Por favor, inténtalo de nuevo.";
}
}
}
?>
Con este último ejemplo, cierro la serie dedicada a Google reCAPTCHA. Veremos cómo implementar el reCAPTCHA de manera programática. La forma de proceder es muy similar al ejemplo visto anteriormente de V2 clásico con renderizado explícito. Por alguna razón, en la documentación oficial utilizan la definición programática y explícita como si fueran algo diferente, pero en realidad es lo mismo.
Veamos el código:
<!DOCTYPE html>
<html>
<head>
<title>Formulario con reCAPTCHA</title>
<script src="https://www.google.com/recaptcha/enterprise.js?render=6LeeMoomAAAAAHMKm0AlFmALlWkSnHy01VQTy0J0"
data-badge="bottomright"></script>
</head>
<body>
<h1>Formulario con reCAPTCHA V3</h1>
<form id="miForm" action="?" method="post">
<label for="nombre">Nombre:</label>
<input type="text" name="nombre" id="nombre" required><br><br>
<label for="email">Email:</label>
<input type="email" name="email" id="email" required><br><br>
<label for="mensaje">Mensaje:</label>
<textarea name="mensaje" id="mensaje" required></textarea><br><br>
<!-- Agrega aquí el campo oculto para el token de reCAPTCHA -->
<input type="hidden" name="token" id="recaptchaToken">
<br>
<input type="submit" value="Enviar" onclick="enviarToken(event)">
</form>
<script>
function enviarToken(e) {
e.preventDefault();
grecaptcha.enterprise.ready(async () => {
const token = await grecaptcha.enterprise.execute('6LeeMoomAAAAAHMKm0AlFmALlWkSnHy01VQTy0J0', {action: 'opinion'});
// Lógica para enviar el token al servidor backend
document.getElementById("recaptchaToken").value = token;
document.getElementById("miForm").submit();
});
}
</script>
</body>
</html>
Si has leido todo el artículo no encontrarás muchas novedades. Tenemos la carga del script de la API exactamente igual que en la versión con botón. Añadimos un campo oculto donde se insertará por código Javascript el token. El botón llama a una función encargada de enviar el formulario con el token insertado. Nada nuevo que no hayamos visto anteriormente.
En la documentación oficial se muestra esta misma función para enviar el token. Solo he añadido las dos últimas líneas debajo de la promesa para insertar el token en el campo oculto del formulario y la orden "submit" que envía el formulario.
<script>
function enviarToken(e) {
e.preventDefault();
grecaptcha.enterprise.ready(async () => {
const token = await grecaptcha.enterprise.execute('6LeeMoomAAAAAHMKm0AlFmALlWkSnHy01VQTy0J0', {action: 'opinion'});
// Lógica para enviar el token al servidor backend
document.getElementById("recaptchaToken").value = token;
document.getElementById("miForm").submit();
});
}
</script>
De la misma forma que la función vista en V3 clásico, recibe por parámetro el evento que utiliza para llamar el método preventDefault()
y evitar el envío predeterminado del formulario.
La manera de manejar las promesas aquí se ha modernizado. Mientras que en V3 clásico encontramos .then
,
ahora se utiliza la palabra clave await
para esperar el resultado de grecaptcha.enterprise.execute()
.
Ambas formas son válidas y funcionarán correctamente. El uso de then()
es más común en el código JavaScript tradicional,
mientras que await
proporciona una sintaxis más moderna y legible para manejar operaciones asincrónicas en funciones asíncronas.
Se define una función flecha (arrow function) como argumento de grecaptcha.enterprise.ready()
. La función flecha se
ejecutará cuando grecaptcha.enterprise esté listo.
Dentro de la función flecha, se declara una constante token.
Se utiliza la palabra clave await
para esperar a que se resuelva la promesa devuelta por grecaptcha.enterprise.execute()
. Esto indica que la ejecución se detendrá hasta que el valor de la promesa esté disponible.
grecaptcha.enterprise.execute()
es llamada con la clave del sitio reCAPTCHA y un
objeto que contiene la propiedad action con valor 'opinion'. El valor resuelto de la promesa es asignado
a la constante "token" que será asignado al campo oculto y finalmente el formulario es enviado al servidor donde
se evaluará el reRECAPTCHA.
En el lado del servidor, utilizamos el mismo código que en ocasiones anteriores.
<?php
// Comprobar que se ha enviado el formulario
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Comprobar que se ha proporcionado la clave secreta de reCAPTCHA
if (!empty($_POST['token'])) {
// Verificar la respuesta de reCAPTCHA con la clave secreta y la respuesta del usuario
$respuesta_recaptcha = $_POST['token'];
$clave_secreta_recaptcha = '6LeeMoomAAAAAE5GPNfj-rF28G9KFyRqkImP-RcJ';
$respuesta = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret=' .
urlencode($clave_secreta_recaptcha) . '&response=' . urlencode($respuesta_recaptcha));
$respuesta = json_decode($respuesta);
//mostrar objeto JSON con el resultado. Sólo en depuración.
echo "# Respuesta de la API reCAPTCHA Enterprise" . "<br>";
echo "<pre>";
print_r($respuesta);
echo "</pre>";
// Verificar si el reCAPTCHA es correcto
if ($respuesta->success && $respuesta->action == "opinion" && $respuesta->score > .6 ) {
// Aquí vendría el procesamiento adicional de los datos al superar el reCAPTCHA
// ...
echo "¡El formulario se ha enviado correctamente!";
} else {
// El reCAPTCHA no fue válido, mostrar un mensaje de error
echo "Error: reCAPTCHA inválido. Por favor, inténtalo de nuevo.";
}
}
}
?>
Puedes descargar los ejemplos completos desde el repositorio de github pulsando aquí. No olvides poner tus propias claves.