Usar Recaptcha en ASP.NET MVC (desde cero)

Buenas! En este post vamos a ver como usar Recaptcha en ASP.NET MVC. Pero, antes que nada permitidme una aclaración: Si estás buscando integrar rápidamente Recaptcha en tu proyecto que sepas que puedes usar MvcRecaptcha o también el helper que viene en MVC3. Pero vamos a ver como hacerlo desde cero. ¿Por que? Pues simplemente porque me parece un buen ejemplo didáctico. Pero insisto: ya hay soluciones hechas, eso es sólo para ver como podríamos hacerlo desde cero

Añadir el captcha en una vista es sumamente sencillo: basta con incluir un tag <script> y dejar que él haga todo. También se puede crear usando javascript (lo que es útil si se quiere crear el captcha sólo si se cumplen ciertas condiciones en tiempo de ejecución), pero no vamos a verlo aquí (todos los detalles están en http://code.google.com/intl/ca/apis/recaptcha/docs/display.html en el apartado de “Ajax API”).

Para añadir recaptcha en nuestra página basta simplemente con añadir el siguiente código script:

<script type="text/javascript"
src="http://www.google.com/recaptcha/api/challenge?k=CLAVE_PUBLICA">
</script>

Este tag <script> renderizará el captcha en la posición donde se incluya.

Vamos a crearnos un helper que nos genere este tag script. El código es trivial:

public static class RecaptchaExtensions
{
public static IHtmlString Recaptcha(this HtmlHelper @this)
{
return Recaptcha(@this, "RecaptchaPublicKey");
}
public static IHtmlString Recaptcha(this HtmlHelper @this, string publicKeyId)
{
var publicKey = ConfigurationManager.AppSettings[publicKeyId];
return DoRecaptcha(@this, publicKey);
}

private static IHtmlString DoRecaptcha(this HtmlHelper @this, string publicKey)
{
var tagBuilder = new TagBuilder("script");
tagBuilder.Attributes.Add("type", "text/javascript");
tagBuilder.Attributes.Add("src", string.Concat("http://www.google.com/recaptcha/api/challenge?k=", publicKey));

return MvcHtmlString.Create(tagBuilder.ToString(TagRenderMode.Normal));
}
}

El método que realmente realiza el trabajo es el método privado DoRecaptcha, que usa un objeto TagBuilder para construir el tag <script>. Fijaos en que el valor de retorno de las funciones del helper es IHtmlString.

La función Recaptcha del helper recibe un parámetro que es el nombre del <appSetting> donde hay la clave pública de Recaptcha (hay una versión sin parámetreos que usa el <appSetting> cuya clave sea RecaptchaPublicKey.

Usar el helper es muy sencillo:

<div>
Necesitamos asegurarnos que eres humano. Actualmente sólo aceptamos
<i>Humanos estándar</i>:
@Html.Recaptcha()
</div>

Perfecto! Estamos listos para lo realmente interesante: Comprobar que el resultado que entra el usuario es válido.

Para ello, si consultamos la página donde se describe el proceso de verificación veremos que necesitamos 4 valores:

  1. La IP del cliente
  2. La clave privada de Recaptcha
  3. Dos valores adicionales, llamados challenge y response que nos envía recaptcha (son campos añadidos al formulario). Los nombres de los dos campos son recaptcha_challenge_field y recaptcha_response_field.

Bueno, para validar que el usuario ha dado de alta el captcha, lo podríamos hacer de muchas maneras, pero yo he escogido un filtro de acción. Eso me va a permitir decorar la acción del controlador de la siguiente manera:

[HttpPost]
[Recaptcha(Name="Captcha")]
public ActionResult Register(RegisterModel model)
{
//...
}

Si la validación con Recaptcha es errónea el flitro dejará un error en ModelState con la clave indicada en el parámetro Name (aquí el mensaje es fijo, pero por supuesto podría ser variable). El filtro lo configuraremos para que se ejecute antes de la acción, por lo que, dentro del método Register podremos usar ModelState.IsValid para preguntar si todo está correcto (incluyendo el captcha).

El uso de un filtro de acción es interesante porque elimina toda esa lógica de comprobación de la acción del controlador.

Bueno, si revisamos de nuevo la documentación de Recaptcha, vemos que debemos usar los 4 valores mencionados anteriormente y realizar un POST a la dirección http://www.google.com/recaptcha/api/verify. La respuesta de este POST nos indicará si la validación ha sido correcta (la primera línea valdrá true) o ha sido incorrecta (valdrá false). ¡Y ya está!

Para crear el filtro, derivamos de la clase ActionFilterAttribute y redefinimos el método OnActionExecuting, para que se ejecute justo ANTES de la acción del controlador:

public class RecaptchaAttribute : ActionFilterAttribute
{
public string Name { get; set; }

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
var request = filterContext.RequestContext.HttpContext.Request;
var challenge = request.Form["recaptcha_challenge_field"];
var response = request.Form["recaptcha_response_field"];
const string postUrl = "http://www.google.com/recaptcha/api/verify";
var result = PerformPost(request.UserHostAddress, challenge, response, postUrl);
if (!result)
{
filterContext.Controller.ViewData.ModelState.AddModelError
(Name ?? string.Empty, "Recaptcha incorrecto");
}
}

}

Este es el código básico: Recogemos los dos campos recaptcha_challenge_field y recaptcha_response_field, realizamos el POST y si el resultado NO es correcto añadimos un error usando el método AddModelError de ModelState.

El método PerformPost sería tal y como sigue:

private bool PerformPost(string remoteip, string challenge, string response, string postUrl)
{
var request = WebRequest.Create(postUrl);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
var stream = request.GetRequestStream();
var privateKey = ConfigurationManager.AppSettings["RecaptchaPrivateKey"];
using (var sw = new StreamWriter(stream))
{
const string data = "privatekey={0}&remoteip={1}&challenge={2}&response={3}";
sw.Write(data, privateKey, remoteip, challenge, response);
}
var recaptchaResponse = request.GetResponse();
string recaptchaData = null;
var recaptchaStream = recaptchaResponse.GetResponseStream();
if (recaptchaStream != null)
{
using (var sr = new StreamReader(recaptchaStream))
{
recaptchaData = sr.ReadToEnd();
}
return ParseResponse(recaptchaData);
}
else return false;
}

Usamos la clase WebRequest para realizar una petición POST con los campos indicados. Fijaos en la definición de la variable data que contiene las variables en el formato típico de post: nombre=valor&nombre=valor&… Luego simplemente volcamos esa variable en el stream de la request del objeto WebRequest.

Finalmente recogemos la respuesta, la guardamos toda en una cadena y la parseamos con el método ParseResponse que es tal y como sigue:

private static bool ParseResponse(string recaptchaData)
{
var reader = new StringReader(recaptchaData);
var first = reader.ReadLine();
var result = false;
if (first != null)
{
first = first.ToLowerInvariant();
bool.TryParse(first, out result);
}

return result;
}

Más simple imposible: leemos la primera línea y miramos si es true o false. Esa primera línea nos indica si ha ido bien o mal la validación del captcha.

Y listos! Por supuesto en la vista podemos usar Html.ValidationMessage para añadir el mensaje de error en caso de que la validación del captcha sea incorrecta:

@Html.ValidationMessage("Captcha")

El lugar donde coloquemos este llamada a Htm.ValidationMessage es donde aparecerá el mensaje de error en caso de que la validación del captcha sea incorrecta. Por supuesto el parámetro de ValidationMessage es la misma cadena que el valor del atributo Name del ActionFilter (en mi caso Captcha).

Nos falta ver el código de la acción del controlador, pero no tiene ningún secreto:

[HttpPost]
[Recaptcha(Name="Captcha")]
public ActionResult Register(RegisterModel model)
{
if (ModelState.IsValid)
{
// Creamos el usuario y lo autenticamos
}
// Si llegamos aquí hay algun error (puede ser el captcha
// puede ser cualquier otro).
return View(model);
}

Una prueba rápida nos permite ver que efectivamente si el usuario falla el captcha aparece el mensaje de error:

image

Y eso es todo!

En este post hemos visto como usar un ActionFilter para integrar la validación de Recaptcha en nuestro site de forma sencilla y fácil.

Insisto en lo que os he dicho al principio: hay soluciones ya hechas para integrar Recaptcha, pero a veces está bien ver las cosas desde cero, saber como funcionan e intentar ver como afrontarlas, no? Porque si siempre nos lo dan todo masticado… que gracia tiene?

Un saludo! 😀

2 comentarios sobre “Usar Recaptcha en ASP.NET MVC (desde cero)”

Responder a jmaguilar Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *