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 πŸ™‚

Subscribe
Notify of
guest
45 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
K D Services
K D Services
2 years ago

good work….

Ravi Tamada
2 years ago
Reply to  K D Services

Thank you:)

Hemant
Hemant
2 years ago
Reply to  Ravi Tamada

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

Ravi Tamada
2 years ago
Reply to  Hemant

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

Tigani
Tigani
2 years ago

nice tutorial and thanks for newsection…thanks

Ravi Tamada
2 years ago
Reply to  Tigani

You are welcome πŸ™‚

Sonu
Sonu
2 years ago

very informative

Ravi Tamada
2 years ago
Reply to  Sonu

Thank you

Mahe
Mahe
2 years ago

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.

Ravi Tamada
2 years ago
Reply to  Mahe

I have tried but couldn’t find any method.

Ravi
Ravi
2 years ago

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

hamy
hamy
2 years ago

thats a perfact solution thanks brother for sharing codeeeee

hamy
hamy
2 years ago

thanks for sharing code ravi

Ravi Tamada
2 years ago
Reply to  hamy

You are welcome Hamy πŸ™‚

Hemant
Hemant
2 years ago
Reply to  Ravi Tamada

Ravi you are a good teacher for here…

Ravi Tamada
2 years ago
Reply to  Hemant

Thank you

Chetan R shanbag
Chetan R shanbag
2 years ago

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

Ravi Tamada
2 years ago

You need to add MyAppCaptcha to your manifest application tag.

mohab magdy
mohab magdy
2 years ago

what can i do if i dont have a server ?

mohab magdy
mohab magdy
2 years ago

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

Dummy User
Dummy User
2 years ago

good simplification methods

Ravi Tamada
2 years ago
Reply to  Dummy User

Thank you

Hemant
Hemant
2 years ago

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

Hemant
Hemant
2 years ago

What is the latest framework for android

Ragesh Antony
Ragesh Antony
2 years ago

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

Manu
Manu
2 years ago

cool man, very useful staff

Ravi Tamada
2 years ago
Reply to  Manu

Thank you

Ragesh Antony
Ragesh Antony
2 years ago

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

cointoneum io
cointoneum io
2 years ago

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)

Ravi Tamada
2 years ago
Reply to  cointoneum io

Have you deployed the PHP code on your server?

cointoneum io
cointoneum io
2 years ago
Reply to  Ravi Tamada

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

Ravi Tamada
2 years ago
Reply to  cointoneum io

You are welcome πŸ™‚

akhil
akhil
2 years ago
Reply to  cointoneum io

Have u got the solution

akhil
akhil
2 years ago

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

Tripti Tyagi
2 years ago

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

amirah
2 years ago

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!

jignesh vaghasiya
2 years ago

Hello Ravi,

thank you for share in Such nice Demo, its working properly in my app.

Thank Once again

Ravi Tamada
2 years ago

You are welcome Jignesh πŸ™‚

sai saanvi
2 years ago

I have tried this. This is mostly used to keep safety for our apps and webpages prevent from spamming….. Thanks for sharing the great information. It helps me a lot.

Patricia M. jessy
Patricia M. jessy
1 year ago
Reply to  sai saanvi

watch Mary Poppins Returns HD 2018 (available 1080p) . with the best quality and all languange….
here–>> POPCORNHDMOVIE1.BLOGSPOT.COM

Dawinder Singh
Dawinder Singh
1 year ago

Above code is old I think. Google update recaptcha link and this is not working with new one. It will be great help if you update it with new approach.

Menglim S
Menglim S
1 year ago

how about iOS?

pheaktra
pheaktra
1 year ago

best article ever 😍😍

Ravi Tamada
1 year ago
Reply to  pheaktra

Thank You πŸ™‚

Sunny Bundel
2 months ago

thats a perfact solution thanks brother for sharing the code..

45
0
Would love your thoughts, please comment.x
()
x