Todo mundo sabe que sou das antigas, sou véio! Comecei no desenvolvimento há um bom tempo. Vi o Google lançar diversas ferramentas que ainda estão por aí (pelo menos algumas). O reCAPTCHA é uma delas. Acompanhei desde a versão 1, depois a v2, v3...
Quando migrei os serviços de uma empresa para o Google Cloud, tive meu primeiro contato com o reCAPTCHA Enterprise, bonito, robusto! Se não me engano, a versão geral foi lançada em 2022, e hoje em dia muita coisa já roda com ele.
Além de oferecer uma segurança muito maior no backend, ele dá um suporte essencial na proteção de APIs. Aí pensei:
“E se eu fizer um teste no Android com Kotlin e backend em PHP? Será que rola?”
Neste artigo, irei ilustrar passo a passo como integrar o reCAPTCHA Enterprise do Google em um projeto Android nativo (Kotlin) com backend em PHP (Slim Framework).

recaptcha-verifier{
"type": "service_account",
"project_id": "example-project",
"private_key_id": "123abc456def789ghi012jkl345mno678pqr901",
"private_key": "-----BEGIN PRIVATE KEY-----\nFAKEPRIVATEKEYFAKEPRIVATEKEYFAKEPRIVATEKEYFAKE\n-----END PRIVATE KEY-----\n",
"client_email": "[email protected]",
"client_id": "000000000000000000000",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/service-account%40example-project.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
Utilizei a seguinte documentação para criar o código fonte deste tópico.
composer require google/cloud-recaptcha-enterprise
config.php)<?php
return [
"jwt_secret" => "mocked_jwt_secret_key_123456789",
"jwt_secret_refresh" => "mocked_refresh_secret_key_987654321",
"db_settings" => [
"host" => "mocked-db-host",
"database" => "mocked_database",
"username" => "mocked_user",
"password" => "mocked_password"
],
"recaptcha_google_key" => <<<'JSON'
{
"type": "service_account",
"project_id": "mocked-project-id",
"private_key_id": "mockedprivatekeyid1234567890abcdef",
"private_key": "-----BEGIN PRIVATE KEY-----\nMOCKEDPRIVATEKEYDATA1234567890ABCDEF\n-----END PRIVATE KEY-----\n",
"client_email": "[email protected]",
"client_id": "000000000000000000000",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/mocked-service-account%40mocked-project-id.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}
JSON
];
src/Helpers/RecaptchaHelper.php)<?php
namespace Helpers;
use Google\Cloud\RecaptchaEnterprise\V1\Client\RecaptchaEnterpriseServiceClient;
use Google\Cloud\RecaptchaEnterprise\V1\CreateAssessmentRequest;
use Google\Cloud\RecaptchaEnterprise\V1\Event;
use Google\Cloud\RecaptchaEnterprise\V1\Assessment;
use Google\Cloud\RecaptchaEnterprise\V1\TokenProperties\InvalidReason;
use Exception;
class RecaptchaHelper
{
public static function isValid(string $token, string $siteKey): bool
{
global $config;
if (empty($config['recaptcha_google_key'])) {
error_log('[Recaptcha] Chave de conta de serviço não definida no config.');
return false;
}
// Cria um arquivo temporário com o conteúdo da chave
$tmpPath = tempnam(sys_get_temp_dir(), 'recaptcha_key_');
file_put_contents($tmpPath, $config['recaptcha_google_key']);
putenv("GOOGLE_APPLICATION_CREDENTIALS={$tmpPath}");
// Extrai project_id do JSON
$keyData = json_decode($config['recaptcha_google_key'], true);
$projectId = $keyData['project_id'] ?? null;
if (!$projectId) {
error_log('[Recaptcha] Project ID não encontrado no JSON.');
return false;
}
try {
$client = new RecaptchaEnterpriseServiceClient();
$projectName = $client->projectName($projectId);
$event = (new Event())
->setSiteKey($siteKey)
->setToken($token);
$assessment = (new Assessment())->setEvent($event);
$request = (new CreateAssessmentRequest())
->setParent($projectName)
->setAssessment($assessment);
$response = $client->createAssessment($request);
$props = $response->getTokenProperties();
if (!$props->getValid()) {
error_log('[Recaptcha] Token inválido: ' . InvalidReason::name($props->getInvalidReason()));
return false;
}
return $response->getRiskAnalysis()->getScore() >= 0.5;
} catch (Exception $e) {
error_log('[Recaptcha] Erro: ' . $e->getMessage());
return false;
}
}
}
if (!RecaptchaHelper::isValid($recaptchaToken, $recaptchaSiteKey)) {
//Não foi possível validar sua ação.
}


[versions]
...
recaptcha = "18.7.0-beta01"
[libraries]
...
recaptcha = { module = "com.google.android.recaptcha:recaptcha", version.ref = "recaptcha" }
dependencies {
...
implementation(libs.recaptcha)
}
package br.com.fbsantos.util
import android.app.Application
import android.util.Log
import com.google.android.recaptcha.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
object RecaptchaHelper {
private const val SITE_KEY = "6MockT0kenAAAAAFakeRecaptcha4TestOnly!!"
private var recaptchaClient: RecaptchaClient? = null
private val recaptchaScope = CoroutineScope(Dispatchers.IO)
fun initialize(application: Application) {
recaptchaScope.launch {
try {
recaptchaClient = Recaptcha.fetchClient(application, SITE_KEY)
Log.d("reCAPTCHA", "Cliente inicializado")
} catch (e: RecaptchaException) {
Log.e("reCAPTCHA", "Erro ao inicializar: ${e.message}", e)
}
}
}
private fun executeLoginAction(onTokenReceived: (String?, String?) -> Unit) {
val client = recaptchaClient ?: return onTokenReceived(null, null)
recaptchaScope.launch {
try {
client.execute(RecaptchaAction.LOGIN)
.onSuccess { token ->
onTokenReceived(token, SITE_KEY)
}
.onFailure { exception ->
Log.e("reCAPTCHA", "Erro na execução: ${exception.message}", exception)
onTokenReceived(null, null)
}
} catch (e: Exception) {
Log.e("reCAPTCHA", "Exceção inesperada: ${e.message}", e)
onTokenReceived(null, null)
}
}
}
fun exec(
before: (() -> Unit)? = null,
after: (() -> Unit)? = null,
onSuccess: (String, String) -> Unit,
onError: (String) -> Unit
) {
before?.invoke()
executeLoginAction { token, siteKey ->
after?.invoke()
if (token != null && siteKey != null) {
onSuccess(token, siteKey)
} else {
onError("Falha ao validar reCAPTCHA. Tente novamente.")
}
}
}
}
package br.com.fbsantos
import android.app.Application
import br.com.fbsantos.util.RecaptchaHelper
class App : Application() {
override fun onCreate() {
super.onCreate()
RecaptchaHelper.initialize(this)
}
}
Button(
onClick = {
RecaptchaHelper.exec(
before = { setFormEnabled(false) },
after = { setFormEnabled(true) },
onSuccess = { token, siteKey ->
onLoginClicked(token, siteKey)
},
onError = { erro ->
setError(erro)
}
)
},
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(50),
colors = ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.onPrimary
),
enabled = state.isFormEnabled
) {
Text("Entrar")
}
Implementando este processo, firmamos um backend mais seguro, dificultando os invasores com os seguintes pontos:
Eu sei, eu sei, você vai falar que tem custo, que é caro e tudo mais… mas veja você tem até 10.000 avaliações gratuitas por mês!

Não da pra começar assim? Se pra você não dá, ok, tem outras plataformas gratuitas! Mas pra mim dá! E é isso que importa! kkkkkkk…
Valeu, falooow!