Symfony Google Recaptcha

Maestro
Maestro
Jun 10 · 6 min read

Se você ainda não passou pela experiência de criar um formulário no seu sistema e receber 80 mil preenchimentos aleatórios em 24 horas, você não sabe o que é terror. Mas, um dia com certeza vai saber.

Nesse post, vamos aprender como diferenciar uma requisição feita por um ser humano de uma feita por um bot malicioso nos nossos formulários =)

Vamos supor que seu cliente/chefe te pediu pra construir um formulário de contato na sua landing page:

html do formulário

Que envia os dados com método POST pra a action '/envia' no sistema:

action envia

E, por sua vez, recebe os dados do formulário e envia tudo por e-mail pra contato@hefesto.io

Se a gente testar esse código, ele vai enviar o e-mail corretamente pro destino.

Portanto, está funcionando! Certo?

No dia seguinte, você chega no trabalho e descobre que aquele formulário que estava funcionando, funciona até demais e recebeu milhares de requisições com dados válidos.

Desses milhares, apenas algumas unidades são de clientes reais, o resto são dados aleatórios.

O que deixa qualquer cliente louco (principalmente se ele usa um serviço de envio de e-mail pago), portanto precisamos impedir as requisições com dados aleatórios!

Como diferenciar a requisição?

Se os dados fossem inválidos seria simples de resolver, afinal é só validar os campos e ser feliz.

Porém, um email como 'asdvcxz@qwerty.org', pode sim ser um e-mail que existe. Portanto, nosso critério de diferenciação não pode ser os dados.

Aqui, uma abordagem seria adicionar um campo escondido no formulário com um código secreto:

input escondido com o token

E, verificar no back-end se esse campo foi enviado corretamente:

verificando o token

Porém, se seu adversário for um bot em selenium ele vai carregar a página exatamente como um usuário carregaria. Com seu código secreto e tudo mais.

A verdade é que a única diferença entre você e um bot bem feito é o comportamento ao preencher o formulário.

Por exemplo, você precisa clickar nos campos pra digitar. O bot não. Isso significa que o tempo que você leva pra enviar a requisição com certeza será diferente do tempo de um bot.

reCAPTCHA!

Pensando nesses cenários comportamentais, a galera da google criou o famoso reCAPTCHA: uma forma de pontuar as requisições feitas nos seus sistemas a fim de definir quão suspeita ela é.

O processo funciona enviando uma requisição pra google quando a tela carrega no navegador. Ou quando você clicka no botãozinho: "Eu não sou um robô".

Nesse post, vamos ficar com a primeira abordagem por ser a menos invasiva pro usuário final =)

Depois enviamos outra requisição com uma chave secreta e a resposta da primeira requisição pra google quando o formulário é enviado, no back-end.

Nessa segunda requisição a google pontua a partir dos comportamentos do usuário e devolve pra gente uma nota de 0 a 1.

Sendo 0 a nota mais provável pra um bot e 1 a nota mais provável para um usuário real =)

Mãos na massa

O primeiro passo, segundo a documentação da google, é criar a chave secreta vinculada a um site

criando uma chave reCAPTCHA

No nosso caso, como vamos trabalhar com o formulário em ambiente de desenvolvimento definimos localhost como domínio permitido. Em casos reais esse domínio deve ser o domínio do seu site.

Ao clickar em enviar, já temos nossas chaves prontas para serem usadas!

Par de chaves registradas

A chave de site é a que vamos usar quando a página carregar e a chave secreta é a que vamos usar no back-end =)

Agora que temos nossas chaves, podemos adicionar no nosso formulário a primeira requisição quando a página carregar. Isso pode ser feito com um script simples fornecido pela própria google

// CARREGA A API PASSANDO A CHAVE DO SITE
<script src="https://www.google.com/recaptcha/api.js?render=reCAPTCHA_site_key"></script>
// ENVIA A PRIMEIRA REQUISIÇÃO PRA GOOGLE
<script>
grecaptcha.ready(function() {
grecaptcha.execute('reCAPTCHA_site_key', {action: 'homepage'}).then(function(token) {
...
});
});
</script>

Aqui, precisamos substituir a chave reCAPTCHA_site_key pela nossa chave do site, que no nosso caso é: "6Lc5DKgUAAAAAIejovEpTX6XImCrwRlQawRbbyNL"

Nosso código no formulário, fica algo como:

HTML do formulário com o JavaScript do reCAPTCHA

Além disso, ao finalizar a requisição, a google nos retorna um token, que precisará ser enviado pro back-end junto com nosso formulário.

Por isso, vamos adicionar o campo recaptchaToken escondido no nosso formulário

input recaptchaToken escondido

Agora, com um pouquinho de JavaScript ou JQuery conseguimos popular esse campo quando a google devolver o token

<script>
grecaptcha.ready(function() {
grecaptcha.execute('6Lc5DKgUAAAAAIejovEpTX6XImCrwRlQawRbbyNL', {action: 'homepage'}).then(function(token) {
// PREENCHENDO O CAMPO recaptchaToken com JavaScript
document.getElementById('recaptchaToken').value = token;
});
});
</script>

Por fim, quando enviarmos nosso formulário, ele virá com o token do reCAPTCHA.

resgatando o valor do token no controller
exemplo de retorno do token

Portanto, só precisamos mandar esse token de volta pra google quando o formulário for enviado e verificar nossa pontuação.

Isso pode ser feito enviando uma requisição do tipo POST pra URL:

https://www.google.com/recaptcha/api/siteverify

Com nossa chave secreta e a resposta do token. Algo como:

$url = "https://www.google.com/recaptcha/api/siteverify";$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, array(
"secret" => "CHAVE_SECRETA",
"response" => "RESPOSTA_DA_PRIMEIRA_REQUISICAO")
);
$response = curl_exec($ch);
curl_close($ch);
$respostaComNota = json_decode($response);

Porém, a galera da google criou uma biblioteca em PHP já pronta pra isso!

Portanto, ao invés disso, podemos simplesmente pedir a biblioteca pro composer

composer require google/recaptcha "^1.2"
composer install reCAPTCHA

Com a instalação do reCAPTCHA, nosso querido Symfony já cria os parâmetros de configuração no arquivo .env

parâmetros de configuração do recaptcha

Porém, precisamos preencher os valores com as chaves corretamente

GOOGLE_RECAPTCHA_SITE_KEY=6Lc5DKgUAAAAAIejovEpTX6XImCrwRlQawRbbyNL
GOOGLE_RECAPTCHA_SECRET=6Lc5DKgUAAAAAHVitpwWbHuHrvuxFLquD5WaeRNL

Tendo as chaves configuradas, basta pedir o ReCaptcha por injeção de dependência

verificando o token com a biblioteca do reCAPTCHA

Com isso, é só usar o método "verify" passando o token que conseguimos na primeira requisição.

O retorno é um objeto com os dados:

{
"success": true|false, // Se a requisição é valida ou não
"score": number // A pontuação da requisição de 0~1
"action": string // A ação que foi realizada no front
"challenge_ts": timestamp, // Data em que a requisição foi feita
"hostname": string, // De que dominio/ip veio a requisição
"error-codes": [...] // Vem preenchido quando da erro com o motivo
}

Tendo a resposta na mão, bastaria verificarmos se success é verdadeiro e se o score contém um valor plausível (maior ou igual a 0.5). Algo como:

$naoEhUmBot = $resposta->success && $resposta->score >= 0.5;

Porém, a biblioteca também já essa verificação pra gente com o método isSuccess!

Verificando se é um bot ou não com a biblioteca

Caso o token seja inválido, o e-mail não é enviado e lançamos uma Exception

Agora, quem decide se a requisição é valida ou não é a própria google a partir do comportamento do usuário!

Indo além

Daria pra melhorar esse código isolando em serviços os códigos que deixamos no controller e usando os FormTypes do framwork. Além disso, daria pra consumir a chave do site dos parâmetros do Symfony ao invés de digitar direto no twig ;)

E ai, o que você achou do reCAPTCHA com o Symfony? Compartilha com a gente aqui nos comentários!

Ah, o código pronto desse post você encontra lá no meu git =)

Code Maestro

Blog to software developers: daily struggles, solutions and best practices. Blog para desenvolvedores de software: lutas diárias, soluções e boas práticas.

Maestro

Written by

Maestro

Mantido por André Chaves, instrutor na Caelum, co-fundador da Hesfesto Software House, desenvolvedor e apaixonado por automação

Code Maestro

Blog to software developers: daily struggles, solutions and best practices. Blog para desenvolvedores de software: lutas diárias, soluções e boas práticas.