Implementing reCAPTCHA Enterprise in a Flutter App with Spring Boot Backend
Introduction
Online security is a growing concern, with bots and automated attacks targeting mobile applications. reCAPTCHA Enterprise provides an AI-powered security solution to protect your Flutter app from fraudulent activities.
In this guide, we will integrate reCAPTCHA Enterprise into a Flutter application (using recaptcha_enterprise_flutter
) and verify the reCAPTCHA token on a Spring Boot backend.
1. Create a reCAPTCHA Key
To begin, generate a reCAPTCHA site key specifically for mobile applications. This key enables reCAPTCHA to collect user interaction data and assess risk levels. Ensure you create a score-based key for your mobile app.
Follow this guide to create your site key:
🔗 Create a reCAPTCHA Key for Mobile
2. Integrate reCAPTCHA with Your Flutter Frontend
Install Dependencies
Add the recaptcha_enterprise_flutter
package to your pubspec.yaml
:
dependencies:
recaptcha_enterprise_flutter: latest_version
Generate reCAPTCHA Token in Flutter
In your Flutter app, use the recaptcha_enterprise_flutter
package to execute reCAPTCHA and generate an encrypted token.
Define Action Names
When generating a token, specify action names in the action
parameter. Follow these best practices:
✅ Use unique and meaningful names: Instead of "login"
, use "user_login_attempt"
or "checkout_verification"
.
❌ Avoid user-specific data: Do not include personal identifiers in the action name.
Reference guide:
🔗 Action Names for Mobile
Example: Requesting a reCAPTCHA Token in Flutter
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:recaptcha_enterprise_flutter/recaptcha.dart';
import 'package:recaptcha_enterprise_flutter/recaptcha_action.dart';
import 'package:recaptcha_enterprise_flutter/recaptcha_client.dart';
class GoogleCloudRecaptchaService {
GoogleCloudRecaptchaService._privateConstructor();
static final GoogleCloudRecaptchaService _instance =
GoogleCloudRecaptchaService._privateConstructor();
static GoogleCloudRecaptchaService get instance => _instance;
RecaptchaClient? _client;
final String _clientState = "uninitialized";
String? _token;
Future<String?> fetchAndExecute() async {
var errorMessage = "failure";
var result = false;
try {
// Platform-specific site key
final siteKey = Platform.isAndroid
? "Your Android Site Key"
: "Your IOS Site Key
_client = await Recaptcha.fetchClient(siteKey);
result = true;
} on PlatformException catch (err) {
debugPrint('Caught platform exception on init: $err');
errorMessage = 'Code: ${err.code} Message ${err.message}';
} catch (err) {
debugPrint('Caught exception on init: $err');
errorMessage = err.toString();
}
if (result) {
try {
_token = await _client?.execute(RecaptchaAction.LOGIN()); // This is your Action
} on PlatformException catch (err) {
debugPrint('Caught platform exception on execute: $err');
throw Exception('Code: ${err.code} Message ${err.message}');
} catch (err) {
debugPrint('Caught exception on execute: $err');
throw Exception(err.toString());
}
} else {
throw Exception(errorMessage);
}
return _token;
}
String? get token => _token;
}
Once you obtain the token, send it to your backend for verification.
3. Verify reCAPTCHA Token in Your Backend
On the backend, the received token must be validated using reCAPTCHA Enterprise's assessment API.
Follow this guide to create assessments:
🔗 Create reCAPTCHA Assessments
Steps for Backend Verification:
1️⃣ Receive the token from Flutter
2️⃣ Send the token to Google’s reCAPTCHA API
3️⃣ Analyze the risk score and take action accordingly
Backend Setup
In the backend, the received token must be validated using reCAPTCHA Enterprise’s assessment API. The backend will verify the token, analyze the risk score, and take appropriate action based on the score.
1. Setting Up reCAPTCHA API Keys in Spring Boot
First, make sure to store sensitive data such as API keys and project IDs securely using Spring Boot's application.properties
or application.yml
files.
Why Store Keys in Properties Files?
Storing keys directly in code is risky as it exposes sensitive data if the source code is shared. Using properties files ensures better security practices. It also allows easy configuration changes without modifying the code.
Example: Store API Keys in application.properties
# Google reCAPTCHA Enterprise Configuration
recaptcha.api-endpoint=https://recaptchaenterprise.googleapis.com/v1/projects/${projectId}/assessments?key=${apiKey}
recaptcha.project-id=your-google-cloud-project-id
recaptcha.site-key=your-recaptcha-site-key
recaptcha.api-key=your-recaptcha-api-key
recaptcha.expected-action=user_login_attempt
2. Implementing the reCAPTCHA Token Verification
Once the token is sent from the Flutter app, we use Spring Boot WebClient to call Google's reCAPTCHA API and validate the token.
Here’s the Spring Boot Service that will handle the API request:
Example: Verifying reCAPTCHA Token in a Spring Boot Backend
package com.app.test.service.impl;
import com.app.test.config.GoogleCloudProperties;
import com.app.test.service.RecaptchaService;
import com.app.test.service.dto.RecaptchaRequestDTO;
import com.app.test.service.dto.RecaptchaResponseDTO;
import com.app.test.web.rest.errors.BadRequestAlertException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@Service("webClientRecaptchaService")
public class RecaptchaServiceImpl implements RecaptchaService {
private static final Logger log = LoggerFactory.getLogger(RecaptchaServiceImpl.class);
private static final float HIGH_RISK_THRESHOLD = 0.3f;
private static final float MEDIUM_RISK_THRESHOLD = 0.7f;
private final WebClient webClient;
private final GoogleCloudProperties googleCloudProperties;
public RecaptchaServiceImpl(WebClient webClient, GoogleCloudProperties googleCloudProperties) {
this.webClient = webClient;
this.googleCloudProperties = googleCloudProperties;
}
@Override
public RecaptchaResponseDTO createAssessment(RecaptchaRequestDTO recaptchaRequest) throws IOException {
if (recaptchaRequest == null || recaptchaRequest.getToken() == null) {
throw new BadRequestAlertException("Invalid reCAPTCHA request", "recaptcha", "invalid-request");
}
String apiEndpoint = googleCloudProperties.getApiEndpoint();
if (apiEndpoint == null) {
throw new BadRequestAlertException("reCAPTCHA API endpoint not configured", "recaptcha", "missing-endpoint");
}
apiEndpoint = apiEndpoint.replace("${projectId}", googleCloudProperties.getRecaptcha().getProjectId())
.replace("${apiKey}", googleCloudProperties.getRecaptcha().getApiKey());
recaptchaRequest.setRecaptchaSiteKey(googleCloudProperties.getRecaptcha().getSiteKey());
recaptchaRequest.setRecaptchaAction(googleCloudProperties.getRecaptcha().getExpectedAction());
Map<String, Object> requestBody = preparePayload(recaptchaRequest);
return sendRecaptchaRequest(apiEndpoint, requestBody);
}
private RecaptchaResponseDTO sendRecaptchaRequest(String apiEndpoint, Map<String, Object> requestBody) {
RecaptchaResponseDTO response = webClient.method(HttpMethod.POST)
.uri(apiEndpoint)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(requestBody)
.retrieve()
.bodyToMono(RecaptchaResponseDTO.class)
.block();
if (response == null) {
throw new BadRequestAlertException("Null response from reCAPTCHA service", "recaptcha", "null-response");
}
return response;
}
private Map<String, Object> preparePayload(RecaptchaRequestDTO recaptchaRequest) {
Map<String, Object> eventMap = new HashMap<>(Map.of(
"siteKey", recaptchaRequest.getRecaptchaSiteKey(),
"token", recaptchaRequest.getToken(),
"expectedAction", recaptchaRequest.getRecaptchaAction()
));
if (recaptchaRequest.getUserIpAddress() != null) {
eventMap.put("userIpAddress", recaptchaRequest.getUserIpAddress());
}
if (recaptchaRequest.getUserAgent() != null) {
eventMap.put("userAgent", recaptchaRequest.getUserAgent());
}
if (recaptchaRequest.getJa3() != null) {
eventMap.put("ja3", recaptchaRequest.getJa3());
}
return Map.of("event", eventMap);
}
}
4. Interpret reCAPTCHA Scores
reCAPTCHA scores range from 0.0 to 1.0, with higher scores indicating lower risk.
1.0 → Almost certainly a human (low risk ✅)
0.0 → Likely a bot (high risk ❌)
Intermediate Scores (0.1, 0.3, 0.7, 0.9) → Require review
By default, only the scores 0.1, 0.3, 0.7, and 0.9 are available unless you enable billing.
Example Handling Strategy:
Score Range | Suggested Action |
≥ 0.7 | Allow login ✅ |
0.3 - 0.7 | Require additional verification (OTP, email confirmation) ⚠️ |
≤ 0.3 | Reject request or enforce CAPTCHA ❌ |
Example JSON Response from reCAPTCHA API
{
"event":{
"expectedAction":"EXPECTED_ACTION",
"hashedAccountId":"ACCOUNT_ID",
"siteKey":"KEY_ID",
"token":"TOKEN",
"userAgent":"(USER-PROVIDED STRING)",
"userIpAddress":"USER_PROVIDED_IP_ADDRESS"
},
"name":"ASSESSMENT_ID",
"riskAnalysis":{
"reasons":[],
"score":"SCORE"
},
"tokenProperties":{
"action":"USER_INTERACTION",
"createTime":"TIMESTAMP",
"hostname":"HOSTNAME",
"invalidReason":"(ENUM)",
"valid":(BOOLEAN)
}
}
5. Implementing Conditional Logic Based on Score
Once the backend receives the risk score, implement logic to handle different risk levels.
Example: Handling reCAPTCHA Scores in a Spring Boot Backend
private void validateResponse(RecaptchaResponseDTO response, RecaptchaRequestDTO recaptchaRequest) {
if (response == null || response.getTokenProperties() == null || response.getRiskAnalysis() == null) {
throw new BadRequestAlertException("Invalid reCAPTCHA response", "recaptcha", "invalid-response");
}
if (!response.getTokenProperties().isValid()) {
throw new BadRequestAlertException("Invalid reCAPTCHA token", "recaptcha", "invalid-token: " + response.getTokenProperties().getInvalidReason());
}
if (!response.getTokenProperties().getAction().equals(recaptchaRequest.getRecaptchaAction())) {
throw new BadRequestAlertException("reCAPTCHA action mismatch", "recaptcha", "action-mismatch");
}
float score = response.getRiskAnalysis().getScore();
log.debug("reCAPTCHA score: {}", score);
if (score < HIGH_RISK_THRESHOLD) {
throw new BadRequestAlertException("High risk detected", "recaptcha", "high-risk");
} else if (score < MEDIUM_RISK_THRESHOLD) {
throw new BadRequestAlertException("Medium risk detected", "recaptcha", "medium-risk");
} else {
log.info("Low risk interaction detected. Score: {}", score);
}
}
Conclusion
By integrating reCAPTCHA Enterprise into your Flutter app and backend, you can prevent fraudulent interactions while ensuring a smooth user experience. Adjust your logic based on the risk assessment scores to optimize security.
Would you like additional details on handling reCAPTCHA failures or logging assessment data? Let me know!