Google reCAPTCHA in Android Application

Google reCaptcha is integrated into an Android application to protect the application from malicious traffic. It is implemented using The SafetyNet API is used to implement Google reCaptcha.

Working on Google reCaptcha:

By calling the network calls between the Android application, SafetyNet server, and our own server, the validation of the Google reCAPTCHA is done.

  • For reCAPTCHA validation, a request is made by the Android app with Site Key to SafetyNet server.
  • The Site key is used by the SafetyNet server to generate the response by captcha token to the Android app.
  • The Secret key is used to send the Captcha token to the server for validating.
  • The Secret Key is used by the android server to make a request to SafetyNet for validating captcha token.
  • The token response is verified by the SafetyNet, after which the result is returned as a success or a fail.
  • By validating tokens, the Android server notifies the Android app, after which the result is returned as a success or a fail.

Generating the reCAPTCHA Site Key and Secret key:

The API terms of services https://developers.google.com/terms/ should be read carefully before creating the API keys.

  • Before accepting the reCAPTCHA terms and Service, we need to provide the input details of the label and the package name.

Label:

It represents a unique label for the API key. The name of a company or organization is usually used as a label.

Package Name:

It represents the package name of the android application.

  • On the next page, we can find the generated Site key, Secret key, client-side integration code, and server-side code.

Example:

In the below example, we are integrating the Google reCAPTCHA in our Android application.

build.gradle:

In the build.gradle file, we will write the code to add the below SafetyNet and Volley dependencies.

Code:

apply plugin: 'com.android.application'
 
android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.example.radioapp"
        minSdkVersion 23
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets {
        main {
            assets {
                srcDirs 'src/main/assets', 'src/main/res/assets/'
            }
        }
    }
}
 
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support:support-v4:28.0.0'
    implementation 'com.android.support:support-annotations:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation 'com.android.support:design:28.0.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'com.google.zxing:core:3.2.1'
    implementation 'com.android.volley:volley:1.1.0'
    implementation 'com.google.android.gms:play-services-safetynet:15.0.1'
 
 
    android {
        useLibrary 'org.apache.http.legacy'
    }
}

AndroidManifest.xml:

In the AndroidManifest.xml file, we will write the code to add internet permission.

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

Code:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.radioapp">
    <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="28"/>
 
    <uses-permission android:name="android.permission.INTERNET" />
 
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
 
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
 
</manifest>

activity_main.xml:

In the activity_main.xml file, we will write the below code.

Code:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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=".MainActivity">
 
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:text="Generate reCaptcha"
        android:textAppearance="@style/Base.TextAppearance.AppCompat.Large"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.436"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.017" />
 
    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="52dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:text="Verify"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
 
</android.support.constraint.ConstraintLayout>

MainActivity.java:

In the MainActivity.java file, we will write the code to make the client-side integration with SafetyNet server. Here, we will also write the code to get the response in JSON String. The value of the SITE_KEY and SECRET_KEY should be replaced with the actual Site Key and Secret Key. The SafetyNet.getClient() method is called on clicking the button to get the Site Key. On success, the handleSiteVerify() is called for token verification.

To serve the below purpose, we can use the Volley library:

  • To maintain the server calls in a queue, the RequestQueue of Volley library can be used.
  • To get the response as JSON String from the server, the StringRequest is used.
  • To retry the server call if it fails within the time limit, the setRetryPolicy() method is used.

Code:

package com.example.radioapp;
 
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import com.android.volley.DefaultRetryPolicy;
import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.VolleyError;
import com.android.volley.toolbox.StringRequest;
import com.android.volley.toolbox.Volley;
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.JSONObject;
import java.util.HashMap;
import java.util.Map;
 
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
 
    String TAG = MainActivity.class.getSimpleName();
    Button btnverifyCaptcha;
    String SITE_KEY = "6LeEJfkUAAAAAIYTMKZWDPkaIbKO502KMIxPVjqQ";
    String SECRET_KEY = "6LeEJfkUAAAAAF8NYvvqYlSivASvJHKrMAIYzCjT";
    RequestQueue queue;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btnverifyCaptcha = findViewById(R.id.button);
        btnverifyCaptcha.setOnClickListener(this);
 
        queue = Volley.newRequestQueue(getApplicationContext());
    }
 
    @Override
    public void onClick(View view) {
        SafetyNet.getClient(this).verifyWithRecaptcha(SITE_KEY)
                .addOnSuccessListener(this, new OnSuccessListener<SafetyNetApi.RecaptchaTokenResponse>() {
                    @Override
                    public void onSuccess(SafetyNetApi.RecaptchaTokenResponse response) {
                        if (!response.getTokenResult().isEmpty()) {
                            handleSiteVerify(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: " +
                                    CommonStatusCodes.getStatusCodeString(apiException.getStatusCode()));
                        } else {
                            Log.d(TAG, "Unknown Error: " + e.getMessage());
                        }
                    }
                });
 
    }
    protected  void handleSiteVerify(final String responseToken){
        //it is google recaptcha siteverify server
        //you can place your server url
        String url = "https://www.google.com/recaptcha/api/siteverify";
        StringRequest request = new StringRequest(Request.Method.POST, url,
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        try {
                            JSONObject jsonObject = new JSONObject(response);
                            if(jsonObject.getBoolean("success")){
                                //code logic when captcha returns true Toast.makeText(getApplicationContext(),String.valueOf(jsonObject.getBoolean("success")),Toast.LENGTH_LONG).show();
                            }
                            else{
                                Toast.makeText(getApplicationContext(),String.valueOf(jsonObject.getString("error-codes")),Toast.LENGTH_LONG).show();
                            }
                        } catch (Exception ex) {
                            Log.d(TAG, "JSON exception: " + ex.getMessage());
 
                        }
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        Log.d(TAG, "Error: " + error.getMessage());
                    }
                }) {
            @Override
            protected Map<String, String> getParams() {
                Map<String, String> params = new HashMap<>();
                params.put("secret", SECRET_KEY);
                params.put("response", responseToken);
                return params;
            }
        };
        request.setRetryPolicy(new DefaultRetryPolicy(
                50000,
                DefaultRetryPolicy.DEFAULT_MAX_RETRIES,
                DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
        queue.add(request);
    }
}

Output 1:

Output 2:

Output 3:

 

Please Share