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.

android-recaptcha-integration-safetynet-api

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.
android-safetynet-recaptcha-server-validation-flow

2. Obtaining SafetyNet Site Key and Secret

Follow the below steps to obtain your Site Key and Secret.

  1. Goto reCaptcha Signup page and register your app.
  2. Enter label to identify the key. You can give your app name or screen name.
  3. Select the type of reCaptcha. I prefer selecting reCaptcha Android.
  4. Enter your app package name. You can enter multiple app package names for the same key.
  5. 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.

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'
}

3. Add the below resources to respective colors.xml, strings.xml and dimens.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>
<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>
<resources>
    <dimen name="activity_margin">16dp</dimen>
</resources>

4. Open the layout file of your main activity (activity_main.xml and content_main.xml) and add the below sample feedback form.

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="info.androidhive.recaptcha.MainActivity">

    <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>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    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:context="info.androidhive.recaptcha.MainActivity"
    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>

5. Create a new class named MyApplication.java and extend the class from Application. In this class, Volley singleton instances are created.

import android.app.Application;
import android.text.TextUtils;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.toolbox.Volley;

/**
 * Created by ravi on 13/03/18.
 */

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);
        }
    }
}

6. Open AndroidManifest.xml and add MyApplication to <application> tag. We also need to add INTERNET permission.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="info.androidhive.recaptcha">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:name=".MyApplication">
    </application>

</manifest>

7. Finally open MainActivity.java and do the modifications as shown below.

  • 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.
package info.androidhive.recaptcha;

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);
    }
}

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.

android-integrating-google-recaptcha-form

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.

<?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_URL => 'https://www.google.com/recaptcha/api/siteverify',
    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);
?>

If you still have any suggestions or queries, please do comment below.

Have a nice day πŸ™‚

Hi there! I am Founder at androidhive and programming enthusiast. My skills includes Android, iOS, PHP, Ruby on Rails and lot more. If you have any idea that you would want me to develop? Let’s talk: ravi@androidhive.info
  • K D Services

    good work….

    • Thank you:)

      • Hemant

        Hi! I want to create search bar in android app for location search and pick current location by click on icon.

        • This is not the correct article to ask about location. Read the location related article on the same blog.

  • Tigani

    nice tutorial and thanks for newsection…thanks

  • Sonu

    very informative

  • Mahe

    Thanks for the nice tutorial. Is there anyway to set dialog.setcanceledontouchoutside(false) for recaptcha dialog or how to stop recaptcha API when user touches outside of the dialog.

    • I have tried but couldn’t find any method.

  • Ravi

    Hi Ravi, Thanks for sharing this tutorial.
    Can you please share how to integrate CCAvenue in Android.

  • hamy

    thats a perfact solution thanks brother for sharing codeeeee

  • hamy

    thanks for sharing code ravi

    • You are welcome Hamy πŸ™‚

      • Hemant

        Ravi you are a good teacher for here…

  • Chetan R shanbag

    ‘xx.xx.xx..Captcha.MyAppCaptcha.addToRequestQueue(com.android.volley.Request)’ on a null object reference…. Im getting this error

    • You need to add MyAppCaptcha to your manifest application tag.

  • mohab magdy

    what can i do if i dont have a server ?

  • mohab magdy

    i got also Json error : no value for the message how can i solve this error ?

  • Dummy User

    good simplification methods

  • Hemant

    I want to search tab in my application. what will I do for it?

  • Hemant

    What is the latest framework for android

  • Ragesh Antony

    How can I use that “checkbox” type I am not robat in android app (like in web sties)

  • Manu

    cool man, very useful staff

  • Ragesh Antony

    I received error message “Unknown status code 12013” What is this ?

  • cointoneum io

    Hello Ravi,

    Thanks for your wonderful tutorials, it helps me established a lot of useful apps.

    But in this one, I’am getting this error :

    Notice: Trying to get property of non-object in on line 25
    which Line 25 is. if($json->success)

    • Have you deployed the PHP code on your server?

      • cointoneum io

        Yes I did, it’s functioning properly now. I just change a few lines and It’s now working flawlessly. Thanks for this tut ravi

    • akhil

      Have u got the solution

  • akhil

    Hi Ravi
    I am getting
    trying to get the property of non object in php recaptcha at line 25(if($json->success){)

    Please help where i am wrong

  • Now these days Google Captcha Mostly used by Website and apps to stop them scam or spaming

  • reCAPTCHA is a free service that uses an advanced risk analysis engine to protect your app from spam and other abusive actions. Thanks for the post!