Using Google reCAPTCHA in Android Application
Google’s reCaptcha
API protects your website / app from malicious traffic. You might have
seen the reCaptcha integrated on web pages. You can integrating the same
in your Android apps too using SafetyNet API. The
service is free to use and it will show a Captcha to be solved if the
engine suspects user interaction to be a bot instead of human.
In this article, we’ll build a simple feedback form and integrate the captcha to avoid the form submission by bots. Not just the forms, you can integrate the captcha in any module in your app.
2. Add safetynet dependency to your build.gradle and rebuild the project. I am also adding Volley and ButterKnife dependencies. Volley is used to send HTTP call to our server to validate the captcha token on the server side.
3. Add the below resources to respective colors.xml, strings.xml and dimens.xml.
4. Open the layout file of your main activity (activity_main.xml and content_main.xml) and add the below sample feedback form.
5. Create a new class named MyApplication.java and extend the class from Application. In this class, Volley singleton instances are created.
6. Open AndroidManifest.xml and add MyApplication to <application> tag. We also need to add INTERNET permission.
7. Finally open MainActivity.java and do the modifications as shown below.
Run and test the app once. If the app is not working, make sure you are using proper package name, key pair and server endpoint.
If you still have any suggestions or queries, please do comment below.
In this article, we’ll build a simple feedback form and integrate the captcha to avoid the form submission by bots. Not just the forms, you can integrate the captcha in any module in your app.
1. How it Works?
The reCAPTCHA will be validated by making certain network calls between your app, SafetyNet server and your server.- First, you need to obtain the SafetyNet key pair by registering your app. You will get Site Key and Secret.
- Site Key will be integrated in android app and it can be public. Secret should kept on your server and it shouldn’t be exposed.
- When reCaptcha is invoked, it will show the Captcha challenge to user if necessary. In this step it communicates with captcha server and returns User Response Token using Site key.
- Once the User Response Token is received, it is still needs to be validated on the server using Secret key.
- From the mobile app, the User Response Token will be sent to your server. From server, the token will be sent to SafetyNet server along with Secret. Once the SafetyNet server validates the token, it notifies your server with success status.
- Finally, your server notifies the mobile app with the status of captcha i.e success or fail.

2. Obtaining SafetyNet Site Key and Secret
Follow the below steps to obtain your Site Key and Secret.- Goto reCaptcha Signup page and register your app.
- Enter label to identify the key. You can give your app name or screen name.
- Select the type of reCaptcha. I prefer selecting reCaptcha Android.
- Enter your app package name. You can enter multiple app package names for the same key.
- Accept the Terms of Service and click on Register. Once registered, you can notice Site Key and Secret displayed on the screen along with sample code.
3. Creating New Project
1. Create a new project in Android Studio from File ⇒ New Project and select Basic Activity from templates. While creating, use the package name you have registered on reCAPTCHA dashboard.2. Add safetynet dependency to your build.gradle and rebuild the project. I am also adding Volley and ButterKnife dependencies. Volley is used to send HTTP call to our server to validate the captcha token on the server side.
build.gradle
dependencies { implementation fileTree(dir: 'libs' , include : [ '*.jar' ]) implementation 'com.android.support:appcompat-v7:26.1.0' // SafetyNet reCAPTCHA implementation 'com.google.android.gms:play-services-safetynet:11.8.0' // ButterKnife implementation 'com.jakewharton:butterknife:8.8.1' annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1' // Volley implementation 'com.android.volley:volley:1.1.0' } |
colors.xml
<? xml version = "1.0" encoding = "utf-8" ?> < resources > < color name = "colorPrimary" >#00bbd3</ color > < color name = "colorPrimaryDark" >#0097a7</ color > < color name = "colorAccent" >#FF4081</ color > </ resources > |
strings.xml
< resources > < string name = "app_name" >reCAPTCHA</ string > < string name = "feedback" >Feedback</ string > < string name = "hint_feedback" >Enter your feedback here!</ string > < string name = "btn_send" >Send Feedback</ string > < string name = "title_form" >Send us some feedback!</ string > < string name = "desc_form" >Have a suggestion? Fill out the form below and we’ll take a look!</ string > < string name = "message_feedback_done" >Thanks for your feedback. We\'ll get back to you soon!</ string > </ resources > |
dimens.xml
< resources > < dimen name = "activity_margin" >16dp</ dimen > </ resources > |
activity_main.xml
<? xml version = "1.0" encoding = "utf-8" ?> < android.support.design.widget.CoordinatorLayout xmlns:android = "http://schemas.android.com/apk/res/android" android:layout_width = "match_parent" android:layout_height = "match_parent"
> < android.support.design.widget.AppBarLayout android:layout_width = "match_parent" android:layout_height = "wrap_content" android:theme = "@style/AppTheme.AppBarOverlay" > < android.support.v7.widget.Toolbar android:id = "@+id/toolbar" android:layout_width = "match_parent" android:layout_height = "?attr/actionBarSize" android:background = "?attr/colorPrimary" app:popupTheme = "@style/AppTheme.PopupOverlay" /> </ android.support.design.widget.AppBarLayout > < include layout = "@layout/content_main" /> </ android.support.design.widget.CoordinatorLayout > |
content_main.xml
<? xml version = "1.0" encoding = "utf-8" ?> android:layout_width = "match_parent" android:layout_height = "match_parent" android:orientation = "vertical" android:padding = "@dimen/activity_margin" app:layout_behavior = "@string/appbar_scrolling_view_behavior" tools:showIn = "@layout/activity_main" > < LinearLayout android:id = "@+id/layout_feedback_form" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:orientation = "vertical" > < TextView android:layout_width = "match_parent" android:layout_height = "wrap_content" android:text = "@string/title_form" android:textColor = "#666666" android:textSize = "20dp" android:textStyle = "bold" /> < TextView android:layout_width = "match_parent" android:layout_height = "wrap_content" android:text = "@string/desc_form" /> < EditText android:id = "@+id/input_feedback" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_marginTop = "@dimen/activity_margin" android:gravity = "top" android:hint = "@string/hint_feedback" android:lines = "5" /> < Button android:id = "@+id/btn_send" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_marginTop = "@dimen/activity_margin" style = "@style/Widget.AppCompat.Button.Colored" android:text = "@string/btn_send" android:textColor = "@android:color/white" /> </ LinearLayout > < TextView android:id = "@+id/message_feedback_done" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_marginTop = "40dp" android:gravity = "center" android:padding = "@dimen/activity_margin" android:text = "@string/message_feedback_done" android:textSize = "22dp" android:visibility = "gone" /> </ LinearLayout > |
MyApplication.java
import android.app.Application; import android.text.TextUtils; import com.android.volley.Request; import com.android.volley.RequestQueue; import com.android.volley.toolbox.Volley; public class MyApplication extends Application { public static final String TAG = MyApplication. class .getSimpleName(); private RequestQueue mRequestQueue; private static MyApplication mInstance; @Override public void onCreate() { super .onCreate(); mInstance = this ; } public static synchronized MyApplication getInstance() { return mInstance; } public RequestQueue getRequestQueue() { if (mRequestQueue == null ) { mRequestQueue = Volley.newRequestQueue(getApplicationContext()); } return mRequestQueue; } public <T> void addToRequestQueue(Request<T> req, String tag) { // set the default tag if tag is empty req.setTag(TextUtils.isEmpty(tag) ? TAG : tag); getRequestQueue().add(req); } public <T> void addToRequestQueue(Request<T> req) { req.setTag(TAG); getRequestQueue().add(req); } public void cancelPendingRequests(Object tag) { if (mRequestQueue != null ) { mRequestQueue.cancelAll(tag); } } } |
AndroidManifest.xml
<? xml version = "1.0" encoding = "utf-8" ?> package = "info.androidhive.recaptcha" > < uses-permission android:name = "android.permission.INTERNET" /> < application android:name = ".MyApplication" > </ application > </ manifest > |
- Replace SAFETY_NET_API_SITE_KEY value with your own SafetyNet Site key
- Replace URL_VERIFY_ON_SERVER value with your own server URL. This url validates the captcha key for info.androidhive.recaptcha only as it contains the Secret for that package.
- https://api.androidhive.info/google-recaptcha-verfication.php is the url of my server, works for this app only. You will see the code of this endpoint shortly.
- validateCaptcha() shows the Captcha dialog and fetches the User Response Token that needs to be sent to your server for validation.
- verifyTokenOnServer() method sends the received User Response Token to server for validating it using the Secret. The server makes POST request to https://www.google.com/recaptcha/api/siteverify and validates the token.
- Once the token is validated on the server, your server responses a JSON with success status. You need to take further action depending on success status. In this case, the feedback has to be submitted.
MainActivity.java
import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.android.volley.Request; import com.android.volley.Response; import com.android.volley.VolleyError; import com.android.volley.toolbox.StringRequest; import com.google.android.gms.common.api.ApiException; import com.google.android.gms.common.api.CommonStatusCodes; import com.google.android.gms.safetynet.SafetyNet; import com.google.android.gms.safetynet.SafetyNetApi; import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.OnSuccessListener; import org.json.JSONException; import org.json.JSONObject; import java.util.HashMap; import java.util.Map; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity. class .getSimpleName(); // TODO - replace the SITE KEY with yours private static final String SAFETY_NET_API_SITE_KEY = "6Lf8z0sUAAAAAP80KqD1U-3e7M_JlOrgWSms5XDd" ; // TODO - replace the SERVER URL with yours private static final String URL_VERIFY_ON_SERVER = "https://api.androidhive.info/google-recaptcha-verfication.php" ; @BindView (R.id.input_feedback) EditText inputFeedback; @BindView (R.id.layout_feedback_form) LinearLayout layoutFeedbackForm; @BindView (R.id.message_feedback_done) TextView messageFeedbackDone; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind( this ); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); getSupportActionBar().setTitle(getString(R.string.feedback)); getSupportActionBar().setDisplayHomeAsUpEnabled( true ); Toast.makeText(getApplicationContext(), "Always check Android Studio `LogCat` for errors!" , Toast.LENGTH_LONG).show(); } @OnClick (R.id.btn_send) public void validateCaptcha() { String feedback = inputFeedback.getText().toString().trim(); // checking for empty feedback message if (TextUtils.isEmpty(feedback)) { Toast.makeText(getApplicationContext(), "Enter feedback!" , Toast.LENGTH_LONG).show(); return ; } // Showing reCAPTCHA dialog SafetyNet.getClient( this ).verifyWithRecaptcha(SAFETY_NET_API_SITE_KEY) .addOnSuccessListener( this , new OnSuccessListener<SafetyNetApi.RecaptchaTokenResponse>() { @Override public void onSuccess(SafetyNetApi.RecaptchaTokenResponse response) { Log.d(TAG, "onSuccess" ); if (!response.getTokenResult().isEmpty()) { // Received captcha token // This token still needs to be validated on the server // using the SECRET key verifyTokenOnServer(response.getTokenResult()); } } }) .addOnFailureListener( this , new OnFailureListener() { @Override public void onFailure( @NonNull Exception e) { if (e instanceof ApiException) { ApiException apiException = (ApiException) e; Log.d(TAG, "Error message: " + CommonStatusCodes.getStatusCodeString(apiException.getStatusCode())); } else { Log.d(TAG, "Unknown type of error: " + e.getMessage()); } } }); } /** * Verifying the captcha token on the server * Post param: recaptcha-response * Server makes call to https://www.google.com/recaptcha/api/siteverify * with SECRET Key and Captcha token */ public void verifyTokenOnServer( final String token) { Log.d(TAG, "Captcha Token" + token); StringRequest strReq = new StringRequest(Request.Method.POST, URL_VERIFY_ON_SERVER, new Response.Listener<String>() { @Override public void onResponse(String response) { Log.d(TAG, response.toString()); try { JSONObject jsonObject = new JSONObject(response); boolean success = jsonObject.getBoolean( "success" ); String message = jsonObject.getString( "message" ); if (success) { // Congrats! captcha verified successfully on server // TODO - submit the feedback to your server layoutFeedbackForm.setVisibility(View.GONE); messageFeedbackDone.setVisibility(View.VISIBLE); } else { Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); } } catch (JSONException e) { e.printStackTrace(); Toast.makeText(getApplicationContext(), "Json Error: " + e.getMessage(), Toast.LENGTH_LONG).show(); } } }, new Response.ErrorListener() { @Override public void onErrorResponse(VolleyError error) { Log.e(TAG, "Error: " + error.getMessage()); } }) { @Override protected Map<String, String> getParams() { Map<String, String> params = new HashMap<>(); params.put( "recaptcha-response" , token); return params; } }; MyApplication.getInstance().addToRequestQueue(strReq); } } |

4. Validating Captcha Token on Server using PHP
Below is the PHP code to validate the captcha token on the server. You can use your own Secret Key and host the code on your server to make it work for your app.google-recaptcha-verfication.php
<?php $ch = curl_init(); // TODO - Define your SafetyNet Secret in the below line $secretKey = 'Place your SafetyNet Secret here' ; $captcha = isset( $_POST [ 'recaptcha-response' ]) && ! empty ( $_POST [ 'recaptcha-response' ]) ? $_POST [ 'recaptcha-response' ]: '' ; curl_setopt_array( $ch , [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => [ 'secret' => $secretKey , 'response' => $captcha , 'remoteip' => $_SERVER [ 'REMOTE_ADDR' ] ], CURLOPT_RETURNTRANSFER => true ]); $output = curl_exec( $ch ); curl_close( $ch ); $json = json_decode( $output ); $res = array (); if ( $json ->success){ $res [ 'success' ] = true; $res [ 'message' ] = 'Captcha verified successfully!' ; } else { $res [ 'success' ] = false; $res [ 'message' ] = 'Failed to verify captcha!' ; } echo json_encode( $res ); ?> |
Comments
Post a Comment