How to integrate the SDK in your application

v1.x

Android SDK

Adding the Android SDK to your project

  1. Add the Android SDK Binary to Your Project.
    The official repository for hosting the HUMAN Android SDK

The repository can be integrated with Maven, Gradle, or Ivy.

<version_to_download> should be replaced with the correct version.

In Maven, run:

1<dependency>
2
3<groupId>com.perimeterx.sdk</groupId>
4
5<artifactId>msdk</artifactId>
6
7<version><version_to_download></version>
8
9<type>pom</type>
10
11</dependency>

In Gradle, run:

1implementation 'com.perimeterx.sdk:msdk:<version_to_download>'

In Ivy, run:

1<dependency org='com.perimeterx.sdk' name='msdk' rev='<version_to_download>'>
2
3<artifact name='msdk' ext='pom' ></artifact>
4
5</dependency>
  1. Once the binary is added to your project, resync the build files to ensure the package is downloaded.

  2. The SDK is now installed, and there should not be any build errors when compiling your application.

To import and integrate the Android SDK in an Android application edit the project’s build.gradle file and add the following maven and mavenCentral configurations:

allprojects {
repositories {
mavenCentral() //allows PerimeterX's Android SDK to resolve its needed dependencies from maven central
maven {
url "https://perimeterx.jfrog.io/artifactory/px-Android-SDK/" //PerimeterX's Artifactory repository url
}
}
}

Adding the Required Permissions to AndroidManifest

  1. In the AndroidManifest.xml add:
1<uses-permission android:name="android.permission.INTERNET" />
2
3<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

XML example:

1<?xml version="1.0" encoding="utf-8"?>
2
3<manifest xmlns:android="http://schemas.android.com/apk/res/android"
4
5package="com.snackhappy.bambarace">
6
7
8<uses-feature android:name="android.hardware.wifi" android:required="true" />
9
10
11<uses-permission android:name="android.permission.INTERNET" />
12
13<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
14
15
16<application
17
18android:allowBackup="true"
19
20android:icon="@mipmap/ic_launcher"
21
22android:label="@string/app_name"
23
24android:roundIcon="@mipmap/ic_launcher_round"
25
26android:supportsRtl="true"
27
28android:theme="@style/AppTheme">
29
30<activity android:name=".MainActivity">
31
32<intent-filter>
33
34<action android:name="android.intent.action.MAIN" />
35
36<category android:name="android.intent.category.LAUNCHER" />
37
38</intent-filter>
39
40</activity>
41
42</application>
43
44</manifest>

Loading the PX Manager

  1. Import the following classes into your Application class or the main activity:
1import com.perimeterx.msdk.PXManager;
2
3import com.perimeterx.msdk.NewHeadersCallback;
4
5import com.perimeterx.msdk.ManagerReadyCallback;
6
7import com.perimeterx.msdk.PXResponse;
8
9import com.perimeterx.msdk.BackButtonPressedCallBack;
  1. In your onCreate method initialize the PX Manager (using your App ID):
1private final static String PERIMETERX_APPID = "PX12345678";
2
3@Override
4
5protected void onCreate(Bundle savedInstanceState) {
6
7
8PXManager.getInstance()
9
10.setNewHeadersCallback(
11
12new NewHeadersCallback() {
13
14@Override
15
16public void onNewHeaders(HashMap<String, String> headers) {
17
18System.out.println("New headers called back");
19
20}
21
22}
23
24)
25
26.setManagerReadyCallback(new ManagerReadyCallback() {
27
28@Override
29
30public void onManagerReady(HashMap<String, String> headers) {
31
32System.out.println("Manager ready called back");
33
34}
35
36})
37
38.start(this, PERIMETERX_APPID);
39
40}
1private val PERIMETERX_APPID = "PX12345678"
2
3
4override fun onCreate(savedInstanceState: Bundle?) {
5
6super.onCreate(savedInstanceState)
7
8
9PXManager.getInstance()
10
11.setNewHeadersCallback { println("New headers called back") }
12
13.setManagerReadyCallback { println("Manager ready called back") }
14
15.start(this, PERIMETERX_APPID)
16
17
18}
  • When initializing the PXManager instance the setManagerReadyCallback is called when the first set of HTTP headers are available.

  • Setting the setNewHeadersCallback is required to ensures that the PXManager will notify the application that a refresh to the HTTP headers occurred. This is based on the token validity time set in the HUMAN portal.

  • Action in the onNewHeaders method is optional. (reference the code example above).

Adding the HTTP Headers to the HTTP Requests

  1. Once the PX Manager is initialized, add HTTP Headers to your network manager headers.

You are now set to start your requests.

  1. The HTTP headers are retrieved by calling the httpHeaders() method of PXManager.

This returns a hashmap of type Map< String, String>. This map can be used as is or can be iterated over.

It is not recommended for the application to make network calls going through PX Enforcer without PX headers.

Using Volley

To add the HTTP headers to a request in Volley you can override the getHeaders method:

1@Override
2
3public Map<String, String> getHeaders() throws AuthFailureError {
4
5return PXManager.httpHeaders();
6
7}
1@Throws(AuthFailureError::class)
2
3override fun getHeaders(): Map<String?, String?>? {
4
5return PXManager.httpHeaders()
6
7}

Using OKHttp

To add the HTTP headers to a request in OKHttp

1Request.Builder reqBuilder = new Request.Builder().url("https://www.perimeterx.com/");
2
3for (HashMap.Entry<String, String> entry : PXManager.httpHeaders().entrySet()) {
4
5String key = entry.getKey();
6
7String value = entry.getValue();
8
9reqBuilder.addHeader(key, value);
10
11}
1val reqBuilder: Request.Builder = OkHttpClient.Builder().url("https://www.perimeterx.com/")
2
3for (entry in PXManager.httpHeaders().entries) {
4
5val key: String = entry.key
6
7val value: String = entry.value
8
9reqBuilder.addHeader(key, value)
10
11}

Reference Interceptors for advanced integration.

Managing Requests Denied

When a HTTP request is denied because of internal error, a 403 response code is sent in the HTTP response along with a JSON body encoded as a UTF-8 that is parsed and used by the Android SDK. The parsed JSON is then used to render a WebView to either challenge (with CAPTCHA) the visitor, or block the visitor from continuing in the application. When the user solves the CAPTCHA challenge they are returned to the application. If the user fails the CAPTCHA challenge they continue to be challenged until they pass the verification screen.

Implementing the Error Handler

The PXManager object checks the body of the returned error message by calling the checkError() method, and passing in the body object as a UTF-8 encoded string. This returns an object type of PXResponse.

To check whether a 403 status code is coming from PX, run:

1PXResponse PXResponse = PXManager.checkError(body);
2
3if (PXResponse.enforcement().name().equals("NOT_PX_BLOCK")){
4
5//the 403 error was not sent by PX
1val response = PXManager.checkError(body)
2
3if (response.enforcement().name.equals("NOT_PX_BLOCK")) {
4
5//the 403 error was not sent by PX
6
7}
1final String body = new String(error.networkResponse.data, UTF_8);
2
3PXResponse PXResponse = PXManager.checkError(body);
1val body = error.networkResponse.data.toString()
2
3val response = PXManager.checkError(body)

The PXResponse object is processed by calling the handleResponse() method of the PXManager object, and passing two arguments:

PXResponse and CaptchaResultCallback.

The handleResponse() method renders the WebView containing the CAPTCHA challenge.

The CaptchaResultCallback must implement the onCallback() method. onCallback() is called whenever the user solves the challenge or the challenge is canceled.

1PXManager.handleResponse(PXResponse, new CaptchaResultCallback() {
2
3@Override
4
5public void onCallback(Result result, CancelReason reason) {
6
7switch (result) {
8
9case SUCCESS:
10
11Log.i(LOG_TAG, "SUCCESS");
12
13break;
14
15case CANCELED:
16
17Log.i(LOG_TAG, "captcha canceled due to: " + reason);
18
19break;
20
21}
22
23}
24
25});
1PXManager.handleResponse(response) { result, reason ->
2
3when (result) {
4
5CaptchaResultCallback.Result.SUCCESS -> Log.i(LOG_TAG, "SUCCESS")
6
7CaptchaResultCallback.Result.CANCELED -> Log.i(
8
9LOG_TAG, "captcha canceled due to: $reason"
10
11)
12
13}
14
15}

handleResponseSync is the same as handleResponse but blocks the current thread. It can be used for implementing an Interceptor. For example:

1import com.perimeterx.msdk.*
2
3import okhttp3.Interceptor
4
5import okhttp3.Request
6
7import okhttp3.Response
8
9import kotlin.jvm.Throws
10
11import java.io.IOException
12
13
14internal class PXInterceptor: Interceptor {
15
16
17@Throws(IOException::class)
18
19override fun intercept(chain: Interceptor.Chain): Response {
20
21val request = chain.request().newBuilder().build()
22
23return proceedRequestAndResolvePXResponseIfNeeded(chain, request)
24
25}
26
27
28fun proceedRequestAndResolvePXResponseIfNeeded(chain: Interceptor.Chain, request: Request): Response {
29
30do {
31
32// add PX headers (remove old values if exist)
33
34val theRequest = addPXHeaders(removePXHeaders(request))
35
36val response = chain.proceed(theRequest)
37
38val rawResponseBody = response.peekBody(Long.MAX_VALUE).string()
39
40val pxResponse = PXManager.checkError(rawResponseBody)
41
42
43// checkk if the response is NOT_PX_BLOCK
44
45if (pxResponse.enforcement() == PXResponse.EnforcementType.NOT_PX_BLOCK) {
46
47// return the response
48
49return response
50
51}
52
53return try {
54
55// handle PX's response (will show captcha screen to the user)
56
57val result = PXManager.handleResponseSync(pxResponse)
58
59
60// handle the captcha result
61
62when (result.captchaResult) {
63
64CaptchaResult.CAPTCHA_SUCCESS -> {
65
66// the user solved the captcha -> procceed the request again
67
68continue;
69
70}
71
72CaptchaResult.NO_CAPTCHA, CaptchaResult.CAPTCHA_CANCELED, null -> {
73
74// the user didn't solve the captcha -> crate an error responsereturn
75
76createError(response)
77
78}
79
80}
81
82} catch (e: ExecutionException) {
83
84// return error
85
86createError(response)
87
88} catch (e: InterruptedException) {
89
90// return error
91
92createError(response)
93
94}
95
96} while (Thread.currentThread().isAlive)
97
98throw IOException("currnet thread is dead")
99
100}
101
102
103fun addPXHeaders(request: Request): Request {
104
105val builder = request.newBuilder()
106
107PXManager.httpHeaders().forEach {
108
109builder.addHeader(it.key, it.value)
110
111}
112
113return builder.build()
114
115}
116
117
118fun removePXHeaders(request: Request): Request {
119
120val builder = request.newBuilder()
121
122PXManager.httpHeaders().forEach {
123
124builder.removeHeader(it.key)
125
126}
127
128return builder.build()
129
130}
131
132
133fun createError(response: Response): Response {
134
135return response.newBuilder().code(-1).build()
136
137}
138
139}

Depending on how your application is built, an attacker can be blocked on any network call your application makes. The attacker can be blocked while the application is loading, and the only thing the user sees is the splash screen. The attacker can be in a background thread when the application makes API calls to retrieve a session token or to request data from the server to be presented on the screen.

You can choose what screen to display after the blocked user successfully solves a CAPTCHA challenge. The user can be redirected to their previous activity and retry the network calls that were blocked. The user can be returned to their previous activity and have to repeat the action that was blocked. The user can be redirected to a different screen.

Using Volley

With Volley a Response.ErrorListener is implemented and the VolleyError is passed to the the onErrorResponse() method.

1new Response.ErrorListener() {
2
3@Override
4
5// Handles errors that occur due to Volley
6
7
8public void onErrorResponse(final VolleyError error) {
9
10final NetworkResponse networkResponse = error.networkResponse;
11
12if (networkResponse != null && networkResponse.statusCode == 403) // Check the HTTP response status code
13
14{
15
16// Retrieve body for captcha processing as a UTF 8 string.
17
18
19final String body = new String(error.networkResponse.data, UTF_8);
20
21
22PXResponse PXResponse = PXManager.checkError(body);
23
24if (PXResponse.enforcement().name().equals("NOT_PX_BLOCK")){
25
26// Handle non PerimeterX 403 response status code
27
28}
29
30PXManager.handleResponse(PXResponse, new CaptchaResultCallback() {
31
32@Override
33
34public void onCallback(Result result, CancelReason reason) {
35
36switch (result) {
37
38case SUCCESS:
39
40Log.i(LOG_TAG, "SUCCESS");
41
42break;
43
44case CANCELED:
45
46Log.i(LOG_TAG, "captcha canceled due to: " + reason);
47
48break;
49
50}
51
52}
53
54});
55
56}
57
58
59Log.d("Volley", "Error");
60
61results.setText(R.string.error_occurred);
62
63}
64
65}

1Response.ErrorListener() {
2
3// Handles errors that occur due to Volley
4
5fun onErrorResponse(error: VolleyError?) {
6
7val networkResponse = error!!.networkResponse
8
9if (networkResponse != null && networkResponse.statusCode == 403) // Check the HTTP response status code
10
11{
12
13// Retrieve body for captcha processing as a UTF 8 string.
14
15val body = String(error!!.networkResponse.data, UTF_8)
16
17val response = PXManager.checkError(body)
18
19if (response.enforcement().name == "NOT_PX_BLOCK") {
20
21// Handle non PerimeterX 403 response status code
22
23}
24
25PXManager.handleResponse(response) { result, reason ->
26
27when (result) {
28
29CaptchaResultCallback.Result.SUCCESS -> Log.i(LOG_TAG, "SUCCESS")
30
31CaptchaResultCallback.Result.CANCELED -> Log.i(
32
33LOG_TAG,
34
35"captcha canceled due to: $reason"
36
37)
38
39}
40
41}
42
43}
44
45
46Log.d("Volley", "Error")
47
48results.setText(R.string.error_occurred)
49
50}
51
52}

Handling Back Button on CAPTCHA webView

When the application request is blocked by the Enforcer, the SDK takes control and a CAPTCHA page is displayed. Attackers may attempt to use Android’s back button to return to the previous screen and continue malicious behavior. There is an option for you to enable or disable Android’s back button functionality. By default, the SDK enables using the back button. When the back button is enabled, touching the back button returns the attacker to the previous screen. Any additional network calls cause the SDK to block the attacker again. By disabling the back button, the attacker remains on the CAPTCHA page until he solves the CAPTCHA challenge, or until he exits the application and relaunches it. When the application is relaunched, any additional network call is blocked with a CAPTCHA page.

  1. To disable the back button, add the following code to the PXManager object during initialization of the SDK:
1.setBackButtonDisabled(true)
2
3.setBackButtonPressedCallback(new BackButtonPressedCallBack(){
4
5
6@Override
7
8public void onBackButtonPressed(){
9
10Log.i(LOG_TAG, "back button pressed");
11
12}
13
14})

1PXManager.getInstance().setBackButtonDisabled(true)
2
3PXManager.getInstance().setBackButtonPressedCallback {
4
5Log.i(LOG_TAG, "back button pressed");
6
7}

  1. Inside the onBackButtonPressed method you can add the code of your choice to return the blocked user to his previous flow.

WebView Integration

Added in version: v1.15.1

If your application includes a WebView, you need to allow SDK to handle the session connection between the web view and the application.

To do that, after you create a web view instance (or get its reference from the view), call:

1PXWebView.pxInit(webView, webViewClient);

1PXWebView.pxInit(webView, webViewClient)

  • The first parameter is your web view instance.

  • The second parameter is optional and can be NULL. If you want to receive events from the web view, you should pass your web view client instance.

The SDK depends on Javascript. You should not disable Javascript in your web view’s settings.

Adding Custom Parameters

Custom parameters allow you to collect and send specific app or device information to be used later for aggregations and intelligence.

Using the PXManager setCustomParameters() method you can specify up to 10 custom parameters to be sent to servers on every network request.

setCustomParameters() accepts a Map of type [String, String] with all the keys beginning with custom_param followed by a number (1-10).

The setCustomParameters() method is set once before calling the start method.

The following example adds 2 custom parameters:

1@Override
2
3protected void onCreate(Bundle savedInstanceState) {
4
5Map<String, String> customParams = new HashMap<>();
6
7customParams.put("custom_param1", "John Bradely");
8
9customParams.put("custom_param2", "New York");
10
11
12PXManager.getInstance()
13
14.setCustomParameters(customParams)
15
16.start(this, PERIMETERX_APPID);

1override fun onCreate(savedInstanceState: Bundle?) {
2
3super.onCreate(savedInstanceState)
4
5
6val customParams: MutableMap<String, String> = HashMap()
7
8customParams["custom_param1"] = "John Bradely"
9
10customParams["custom_param2"] = "New York"
11
12
13PXManager.getInstance().setCustomParameters(customParams)
14
15PXManager.getInstance().start(this, PERIMETERX_APPID)
16
17}

Support Account Defender (From version 1.16.5)

The Account Defender Manager instance is retrieved by calling PXAccountDefenderManager.getInstance(context) with a context object. To enable the feature, a user ID must be set. That can be done by calling the PXAccountDefenderManager.setUserID function and providing the user ID. When the user ID is changed, the function should be called again with the updated user ID. If the user logs out, the function should be called with nil. The function should be called after the PXManager.startWith function.

To register outgoing URL requests, call the PXAccountDefenderManager.registerOutgoingUrlRequest function with the URL. This function should be called only when Account Defender is enabled (user ID is set).

User Agent Convention

The application’s user agent is used to identify and differentiate between application versions. For accurate detection it is important that the user agent your application reports is informative. Here is the recommended convention:

Doctor App

The “Doctor App” is a tool to help you verify the mobile SDK integration by simulating a typical user flow in your application. To enable this feature, add the enableDoctorCheck = true parameter in the start method.

Important

This feature is for development purposes only and should not be shipped with your application to the application store.

Example:

1PXManager.getInstance().start(this, "[YOUR_APP_ID]", true);

Flow:

  1. Welcome screen: In this screen you select whether to start a new test or load the summary of the previous test, if one exists.

  2. Instructions screen: In this screen you get detailed instructions on how the Doctor app works

  3. Test selection screen: In this screen you choose which type of test to execute. Options are:

a. Native app framework - test your native URL requests.

b. Web view framework - test your web view’s URL requests.

After executing either test, you will return to this screen to be able to execute the other test or to continue and review the test results in the summary screen.

  1. Summary screen: In this screen you are able to view the results of the executed tests. You can go into details regarding each test and get troubleshooting tips in case a test failed to help you analyze what is causing this issue.

Important

When you exit the doctor app, your application will also terminate. Just remember to switch the ‘enableDoctorCheck’ parameter to false when you finish validating your integration with mobile SDK.

Validating that PXManager is Loaded Properly

Checking the ManagerReady Callback

To verify that SDK is implemented correctly, print a line to the console when the ManagerReady callback is fired.

1.setManagerReadyCallback(new ManagerReadyCallback() {
2
3@Override
4
5public void onManagerReady(HashMap<String, String> headers) {
6
7System.out.println("Manager ready called back");
8
9}
10
11})

1PXManager.getInstance().setManagerReadyCallback() {
2
3println("Manager ready called back");
4
5}

A log line similar to the example below should be displayed in your Android Monitor window:

109-28 21:13:09.776 3484-3585/com.perimeterx.example I/System.out: Manager ready called back

Checking With ADB

Connect to the device with ADB and run adb logcat | grep InternalManager.

This should produce the following output indicating the SDK has started and is in a ready state:

1$ adb logcat | grep InternalManager
2
309-28 21:13:08.129 3484 3484 I InternalManager: SDK start()
4
509-28 21:13:08.227 3484 3484 I InternalManager: shouldRunCompleteSDKFlow - app version is different - new version: 1.0
6
709-28 21:13:08.227 3484 3484 I InternalManager: SDK shouldRunCompleteSDKFlow
8
909-28 21:13:08.227 3484 3484 I InternalManager: checkSDKEnabled...
10
1109-28 21:13:08.576 3484 3511 I InternalManager: SDK is enabled on server
12
1309-28 21:13:08.576 3484 3511 D InternalManager: Running app init activity
14
1509-28 21:13:09.776 3484 3585 I InternalManager: SDK ready time: 1647

Validating that HTTP Headers Are Part of Your Request

Connections to API endpoint are inspected using a local proxy such as CharlesProxy_. When inspecting requests look for the HTTP header named X-PX-AUTHORIZATION.

Requests to the perimeterx.net domain are also pinned to a specific SSL certificate and cannot be inspected with Charles Proxy. If you have enabled a SSL proxy for all domains you must exclude perimeterx.net.”>

If this header is not present go back to the application code responsible for making the request and review to verify the headers were added to the request properly.

Validating Denied Requests

Denied requests have one of two possible actions, block or challenge, and both should be validated to ensure the request is properly handled.

Console Developer Testing Tools

Developers integrating with the Mobile SDK can be given access to the HUMAN Portal in a developer role. They then have access to the Developer section, which contains the Mobile Enforcement Verification tool. This tool allows a developer the ability to mark their requests to be denied for testing purposes.

The testing tool is necessary to validate denied requests.

Make sure the Enforcer you are testing against is set to blocking mode before proceeding with the instructions.

  1. Access the tool at https://console.humansecurity.com/botDefender/developer.

  2. Locate your test device’s Visitor ID <glossary:VID>, and click the <glossary:VID> Extractor button.

This will launch the <glossary:VID> Extractor model:

  1. Insert your IP address and click Search.

This locates all <glossary:VID>s for your APP ID.

  1. Click Select to return to the tool with the App ID and <glossary:VID> fields populated

  2. When the App ID and <glossary:VID> are available, you can start validating denied requests.

Validating a Challenge Response

To validate a challenge response:

  1. Click CAPTCHA in the test tool and wait for the green toast notification to confirm you are ready to continue.

  2. When the next PX token refresh occurs in your mobile application you will receive the CAPTCHA WebView.

  3. To force a token refresh simply exit and relaunch the application.

  4. Solve the challenge presented and verify that you are able to return to the application and continue using it.

Validating a Blocking Response

To validate a blocking response:

  1. Click Block in the test tool and wait for the green toast notification to confirm you are ready to continue.

  2. When the next PX token refresh occurs in your mobile application you will receive the block WebView.

  3. To force a token refresh simply relaunch the application. Once you are blocked you cannot continue.

  4. To release from blocking, exit the application, click the Clear button, and reload the application to verify that the clean token was received and the application is operating as expected.

Appendix

Configuring an Android Emulator to use Charles Proxy

To configure your Android Emulator:

  1. Set the HTTP proxy settings in the Wi-Fi menu.

  2. In the Wi-Fi menu long press the connected network, select Modify Network, and fill in the proxy settings.

If you are running Charles Proxy on your local machine then use the local IP (not the loopback).

  1. To intercept HTTPS requests follow the guidelines at:

https://www.charlesproxy.com/documentation/using-charles/ssl-certificates.

In Android Nougat special permissions are required to use a certificate added to the trust store.

Deactivating the PX Mobile Sensor Remotely

In certain cases, the PX Mobile Sensor (SDK) may need to be deactivated remotely, and the app run without any sensor intervention.

This can be done based on any combination of the following parameters:

  • AppID

  • Operating System

  • Operating System Version

  • SDK Version

  • App Version

To deactivate the Mobile Sensor, contact assigned to you support person. Support to allow deactivation via the Portal will be added in the future.

Support for Full Screen on Devices with a Display Cutout

The default cutout mode is set to Never, meaning the challenge page will not render on the cutout area:

To change the behaviour to SHORT_EDGES, and have the page render on the entire display area, set the setIsCutoutFullScreen() to true.

The setIsCutoutFullScreen() method is set once before calling the start method.

Full screen on devices with display cutouts is supported on Android version P (API level 28) and above and SDK version 1.7.0 and above.

1@Override
2
3protected void onCreate(Bundle savedInstanceState) {
4
5
6PXManager.getInstance()
7
8.setIsCutoutFullScreen(true)
9
10.start(this, PERIMETERX_APPID);

1override fun onCreate(savedInstanceState: Bundle?) {
2
3super.onCreate(savedInstanceState)
4
5
6PXManager.getInstance().setIsCutoutFullScreen(true)
7
8PXManager.getInstance().start(this, PERIMETERX_APPID);
9
10}

The challenge page will now render over the entire display:

Android SDK React Native

Integration

Adding the Android SDK to your Project:

  1. Add the Android SDK Binary to Your Project with:

(the official repository for hosting the HUMAN Android SDK).

The repository can be integrated with Maven, Gradle, or Ivy.

<version_to_download> should be replaced with the correct version.

In Maven, run:

1<dependency>
2
3<groupId>com.perimeterx.sdk</groupId>
4
5<artifactId>msdk</artifactId>
6
7<version><version_to_download></version>
8
9<type>pom</type>
10
11</dependency>
12
13
14**In Gradle, run:**

1implementation 'com.perimeterx.sdk:msdk:<version_to_download>'

In Ivy, run:

1<dependency org='com.perimeterx.sdk' name='msdk' rev='<version_to_download>'>
2
3<artifact name='msdk' ext='pom' ></artifact>
4
5</dependency>

  1. Once the binary is added to your project, resync the build files to ensure the package is downloaded.

To import and integrate the Android SDK in an Android application edit the project’s build.gradle file and add the following maven and mavenCentral configurations:

allprojects {
repositories {
mavenCentral() //allows PerimeterX's Android SDK to resolve its needed dependencies from maven central
maven {
url "https://perimeterx.jfrog.io/artifactory/px-Android-SDK/" //PerimeterX's Artifactory repository url
}
}
}

The SDK is now installed, and there should not be any build errors when compiling your application.

Adding the Required Permissions to AndroidManifest

In your AndroidManifest.xml add:

1<uses-permission android:name="android.permission.INTERNET" />
2
3<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

XML example:

1<?xml version="1.0" encoding="utf-8"?>
2
3<manifest xmlns:android="http://schemas.android.com/apk/res/android"
4
5package="com.snackhappy.bambarace">
6
7
8<uses-feature android:name="android.hardware.wifi" android:required="true" />
9
10
11<uses-permission android:name="android.permission.INTERNET" />
12
13<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
14
15
16<application
17
18android:allowBackup="true"
19
20android:icon="@mipmap/ic_launcher"
21
22android:label="@string/app_name"
23
24android:roundIcon="@mipmap/ic_launcher_round"
25
26android:supportsRtl="true"
27
28android:theme="@style/AppTheme">
29
30<activity android:name=".MainActivity">
31
32<intent-filter>
33
34<action android:name="android.intent.action.MAIN" />
35
36<category android:name="android.intent.category.LAUNCHER" />
37
38</intent-filter>
39
40</activity>
41
42</application>
43
44</manifest>

Initializing the PX Manager

  1. Import the following classes into your Application class or the main activity:
1import com.perimeterx.msdk.ManagerReadyCallback;
2
3import com.perimeterx.msdk.NewHeadersCallback;
4
5import com.perimeterx.msdk.PXManager;
  1. In your onCreate method initialize the PX Manager (using your App ID):
1@Override
2
3public void onCreate() {
4
5super.onCreate();
6
7SoLoader.init(this, /* native exopackage */ false);
8
9
10PXManager.getInstance()
11
12.setNewHeadersCallback(new NewHeadersCallback() {
13
14@Override
15
16public void onNewHeaders(HashMap<String, String> headers) {
17
18System.out.println("New headers called back");
19
20}
21
22})
23
24.setManagerReadyCallback(new ManagerReadyCallback() {
25
26@Override
27
28public void onManagerReady(HashMap<String, String> headers) {
29
30Intent localIntent = new Intent("managerReady");
31
32Log.i(LOG_TAG, "onManagerReady: " + headers);
33
34}
35
36})
37
38.start(this, "<app_id>");
39
40}

Creating a Bridge For The Native Methods

  1. In your project, add a new class called PXBridge and set it to extend ReactContextBaseJavaModule:
1public class PXBridge extends ReactContextBaseJavaModule {
2
3
4}
  1. Add the class constructor:
1public PXBridge(ReactApplicationContext reactContext) {
2
3super(reactContext);
4
5}
  1. Add the following methods:
1@ReactMethod
2
3public void verifyResponse(ReadableMap body, final Promise promise) {
4
5
6String parsedBody;
7
8try {
9
10JSONObject convertedBody = convertMapToJson(body);
11
12parsedBody = convertedBody.toString();
13
14
15} catch (JSONException e) {
16
17parsedBody = body.toString();
18
19}
20
21
22
23PXResponse PXResponse = PXManager.checkError(parsedBody);
24
25if (PXResponse.enforcement().name().equals("NOT_PX_BLOCK")){
26
27promise.resolve("NotPXBlock");
28
29} else {
30
31PXManager.handleResponse(PXResponse, new ActionResultCallback() {
32
33
34@Override
35
36public void onSuccess() {
37
38System.out.println("onSuccess called ....");
39
40promise.resolve("success");
41
42}
43
44
45@Override
46
47public void onFailure(IOException exception) {
48
49promise.resolve("failure");
50
51}
52
53
54@Override
55
56public void onBlockWindowClosed() {
57
58promise.reject("error", Block Window Closed");
59
60}
61
62});
63
64}
65
66
67}
68
69
70@ReactMethod
71
72public void getHttpHeaders(Promise promise) {
73
74WritableMap map = Arguments.createMap();
75
76
77for (HashMap.Entry<String, String> entry : PXManager.httpHeaders().entrySet()) {
78
79map.putString(entry.getKey(),entry.getValue());
80
81}
82
83promise.resolve(map);
84
85}
86
87
88private static JSONObject convertMapToJson(ReadableMap readableMap) throws JSONException {
89
90JSONObject object = new JSONObject();
91
92ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
93
94while (iterator.hasNextKey()) {
95
96String key = iterator.nextKey();
97
98switch (readableMap.getType(key)) {
99
100case Null:
101
102object.put(key, JSONObject.NULL);
103
104break;
105
106case Boolean:
107
108object.put(key, readableMap.getBoolean(key));
109
110break;
111
112case Number:
113
114object.put(key, readableMap.getDouble(key));
115
116break;
117
118case String:
119
120object.put(key, readableMap.getString(key));
121
122break;
123
124case Map:
125
126object.put(key, convertMapToJson(readableMap.getMap(key)));
127
128break;
129
130case Array:
131
132object.put(key, convertArrayToJson(readableMap.getArray(key)));
133
134break;
135
136}
137
138}
139
140return object;
141
142}
143
144
145private static JSONArray convertArrayToJson(ReadableArray readableArray) throws JSONException {
146
147JSONArray array = new JSONArray();
148
149for (int i = 0; i < readableArray.size(); i++) {
150
151switch (readableArray.getType(i)) {
152
153case Null:
154
155break;
156
157case Boolean:
158
159array.put(readableArray.getBoolean(i));
160
161break;
162
163case Number:
164
165array.put(readableArray.getDouble(i));
166
167break;
168
169case String:
170
171array.put(readableArray.getString(i));
172
173break;
174
175case Map:
176
177array.put(convertMapToJson(readableArray.getMap(i)));
178
179break;
180
181case Array:
182
183array.put(convertArrayToJson(readableArray.getArray(i)));
184
185break;
186
187}
188
189}
190
191return array;
192
193}
194
195
196@Override
197
198public String getName() {
199
200return "PXBridge";
201
202}

The bridge exposes the minimal methods required for the SDK to operate:

  • getHttpHeaders - returns a JSON file containing the HTTP headers.

  • verifyResponse - verifies the 403 response and based on its content - either returns the response unchanged or calls the SDK’s handleBlockResponse method.

  1. Add a new class called PXBridgePackage and set it to extend ReactPackage:
1public class PXBridgePackage implements ReactPackage {
2
3
4}
  1. Add the following methods to the new class:
1@Override
2
3public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
4
5List<NativeModule> modules = new ArrayList<>();
6
7modules.add(new PXBridge(reactContext));
8
9return modules;
10
11}
12
13
14@Override
15
16public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
17
18return Collections.emptyList();
19
20}
  1. In your app’s MainApplication.java, add the PXBridgePackage class to the end of the array returned by the GetPackages() method:
1protected List<ReactPackage> getPackages() {
2
3return Arrays.<ReactPackage>asList(
4
5new MainReactPackage(),
6
7new PXBridgePackage()
8
9);
10
11}

Adding the HTTP Headers to the HTTP Requests

Once the PX Manager is initialized, you need to add the HTTP Headers to every network request your app is making. These headers can be retrieved by calling the PXManager.httpHeaders() method of the SDK, which is exposed to the JavaScript side by the bridge created in the previous step.

Using Axios

To add the  HTTP headers to every request using Axios, use the following example:

1import {NativeModules} from 'react-native';
2
3
4axios.interceptors.request.use(async (config) => {
5
6const pxHeaderResponse = await NativeModules.PXBridge.getHttpHeaders();
7
8for (var prop in pxHeaderResponse) {
9
10config.headers[prop] = pxHeaderResponse[prop];
11
12}
13
14return config;
15
16}, function (error) {
17
18throw error;
19
20});

Using Fetch

To add the HTTP headers to a fetch request, either add the headers directly to each request or create a wrapper like the following example:

1async function callFetch() {
2
3const url = ' ';
4
5const pxHeaderResponse = await NativeModules.PXBridge.getHttpHeaders();
6
7let headers = {};
8
9for (var prop in pxHeaderResponse) {
10
11headers[prop] = pxHeaderResponse[prop];
12
13}
14
15let result;
16
17try {
18
19result = await fetch(url,{
20
21method: 'GET',
22
23headers: headers
24
25})
26
27let jsonResult = await result.json();
28
29return jsonResult;
30
31} catch (e) {
32
33throw e;
34
35}
36
37}

Managing Requests Denied

When a HTTP request is denied because of internal error, a 403 response code is sent in the HTTP response along with a JSON body encoded as a UTF-8. The encoded JSON body is parsed and used by the Android SDK. The parsed JSON is then used to render a WebView to either challenge (with CAPTCHA) the visitor or block the visitor from continuing in the application. If the user solves the presented challenge they will return to the application. If the user fails the CAPTCHA challenge they continue to be challenged until they pass the verification screen.

Implementing the Error Handler

Using Axios

The easiest way to implement the error handler is with Axios’s response interceptor:

1axios.interceptors.response.use((response) => {
2
3return response;
4
5}, async (error) => {
6
7if (error.response.status === 403) {
8
9let result = await NativeModules.PXBridge.verifyResponse(error.response.data);
10
11if (result == "NotPXBlock") {
12
13throw error;
14
15}
16
17} else {
18
19throw error;
20
21}
22
23});

Using Fetch

You can also implement the error handler using a Fetch based wrapper:

1async function callFetch() {
2
3const url = ' ';
4
5const pxHeaderResponse = await NativeModules.PXBridge.getHttpHeaders();
6
7let headers = {};
8
9for (var prop in pxHeaderResponse) {
10
11headers[prop] = pxHeaderResponse[prop];
12
13}
14
15let result;
16
17try {
18
19result = await fetch(url,{
20
21method: 'GET',
22
23headers: headers
24
25})
26
27let jsonResult = await result.json();
28
29return jsonResult;
30
31} catch (error) {
32
33if (error.response.status === 403) {
34
35let result = await NativeModules.PXBridge.verifyResponse(error.response.data);
36
37if (result == "NotPXBlock") {
38
39throw error;
40
41}
42
43} else {
44
45throw error;
46
47}
48
49}
50
51}

Handling Back Button on Captcha webView

When the application request is blocked by the Enforcer, the SDK takes control and a Captcha page is displayed. Attackers may attempt to use Android’s back button to return to the previous screen and continue malicious behavior. There is an option for you to enable or disable Android’s back button functionality. When the back button is enabled, touching the back button returns the attacker to the previous screen. Any additional network calls cause the SDK to block the attacker again. By disabling the back button, the attacker remains on the CAPTCHA page until he solves the Captcha challenge, or until he exits the application and relaunches it. When the application is relaunched, any additional network call is blocked with a Captcha page.

By default, the SDK enables using the back button.

  1. To disable the back button, add the following code to the PXManager object during initialization of the SDK:
1.setBackButtonDisabled(true)
2
3.setBackButtonPressedCallback(new BackButtonPressedCallBack(){
4
5
6@Override
7
8public void onBackButtonPressed(){
9
10Log.i(LOG_TAG, "back button pressed");
11
12}
13
14})
  1. Inside the onBackButtonPressed method you can add the code of your choice to return the blocked user to his previous flow.

Adding Custom Parameters

Custom parameters allow you to collect and send specific app or device information to be used later for aggregations and intelligence.

Using the PXManager*setCustomParameters()` method you can specify up to 10 custom parameters to be sent to PX servers on every network request.

setCustomParameters() accepts a Map of type [String, String] with all the keys beginning with custom_param followed by a number (1-10).

The setCustomParameters() method is set once before calling the start method.

The following example adds 2 custom parameters:

1@Override
2
3public void onCreate() {
4
5super.onCreate();
6
7SoLoader.init(this, /* native exopackage */ false);
8
9
10Map<String, String> customParams = new HashMap<>();
11
12customParams.put("custom_param1", "John Bradely");
13
14customParams.put("custom_param2", "New York");
15
16
17PXManager.getInstance()
18
19.setCustomParameters(customParams)
20
21.start(this, "<app_id>");
22
23
24.. _add-the-perimeterx-http-headers-to-the-applications-http-requests:

WebView Integration

Added in version: v1.15.2

If your application includes a WebView, you need to allow the SDK to handle the session connection between the web view and the application.

To do that, after you create a web view instance (or get its reference from the view), call:

1PXWebView.pxInit(webView, webViewClient);
  • The first parameter is your web view instance.

  • The second parameter is optional and can be NULL. If you want to receive events from the web view, you should pass your web view client instance.

The SDK depends on Javascript. You should not disable Javascript in your web view’s settings.

Support Account Defender (From version 1.16.5)

The Account Defender Manager instance is retrieved by calling PXAccountDefenderManager.getInstance(context) with a context object. To enable the feature, a user ID must be set. That can be done by calling the PXAccountDefenderManager.setUserID function and providing the user ID. When the user ID is changed, the function should be called again with the updated user ID. If the user logs out, the function should be called with nil. The function should be called after the PXManager.startWith function.

To register outgoing URL requests, call the PXAccountDefenderManager.registerOutgoingUrlRequest function with the URL. This function should be called only when Account Defender is enabled (user ID is set).

User Agent Convention

The application’s user agent is used to identify and differentiate between application versions. For accurate detection it is important that the user agent your application reports is informative. Here is the recommended convention:

Doctor App

The “Doctor App” is a tool to help you verify the mobile SDK integration by simulating a typical user flow in your application. To enable this feature, add the enableDoctorCheck = true parameter in the start method.

Important

This feature is for development purposes only and should not be shipped with your application to the application store.

Example:

1[PXManager.sharedInstance startWith:@"[YOUR_APP_ID]" enableDoctorCheck:YES];

Flow:

  1. Welcome screen: In this screen you select whether to start a new test or load the summary of the previous test, if one exists.

  2. Instructions screen: In this screen you get detailed instructions on how the Doctor app works

  3. Test selection screen: In this screen you choose which type of test to execute. Options are:

a. Native app framework - test your native URL requests.

b. Web view framework - test your web view’s URL requests.

After executing either test, you will return to this screen to be able to execute the other test or to continue and review the test results in the summary screen.

  1. Summary screen: In this screen you are able to view the results of the executed tests. You can go into details regarding each test and get troubleshooting tips in case a test failed to help you analyze what is causing this issue.

Important

When you exit the doctor app, your application will also terminate. Just remember to switch the ‘enableDoctorCheck’ parameter to false when you finish validating your integration with the mobile SDK.

Verifying Your Integration

Validating that PXManager is Loaded Properly

Checking the ManagerReady Callback

To verify that SDK is implemented correctly, print a line to the console when the ManagerReady callback is fired.

1.setManagerReadyCallback(new ManagerReadyCallback() {
2
3@Override
4
5public void onManagerReady(HashMap<String, String> headers) {
6
7System.out.println("Manager ready called back");
8
9}
10
11})

A log line similar to the example below should be displayed in your Android Monitor window:

109-28 21:13:09.776 3484-3585/com.perimeterx.example I/System.out: Manager ready called back

Checking With ADB

Connect to the device with ADB and run adb logcat | grep InternalManager.

This should produce the following output indicating the SDK has started and is in a ready state:

1$ adb logcat | grep InternalManager
2
309-28 21:13:08.129 3484 3484 I InternalManager: SDK start()
4
509-28 21:13:08.227 3484 3484 I InternalManager: shouldRunCompleteSDKFlow - app version is different - new version: 1.0
6
709-28 21:13:08.227 3484 3484 I InternalManager: SDK shouldRunCompleteSDKFlow
8
909-28 21:13:08.227 3484 3484 I InternalManager: checkSDKEnabled...
10
1109-28 21:13:08.576 3484 3511 I InternalManager: SDK is enabled on server
12
1309-28 21:13:08.576 3484 3511 D InternalManager: Running app init activity
14
1509-28 21:13:09.776 3484 3585 I InternalManager: SDK ready time: 1647

Validating that HTTP Headers Are Part of Your Request

Connections to your API endpoint can be inspected using a local proxy such as CharlesProxy_.

When inspecting requests look for the HTTP header named X-PX-AUTHORIZATION.

Requests to the perimeterx.net domain are also pinned to a specific SSL certificate and cannot be inspected with Charles Proxy. If you have enabled SSL proxy for all domains you must exclude perimeterx.net.

If this header is not present go back to the application code responsible for making the request and review to verify the headers were added to the request properly.

Validating Denied Requests

Denied requests have one of two possible actions, block or challenge, and both should be validated to ensure the request is properly handled.

Console Developer Testing Tools

Developers integrating with the PX Mobile SDK can be given access to the HUMAN Portal in a developer role. They then have access to the Developer section, which contains the Mobile Enforcement Verification tool. This tool allows a developer to mark their requests to be denied for testing purposes.

The testing tool is necessary to validate denied requests.

Make sure the enforcer you are testing against is set to blocking mode before proceeding with the instructions.

  1. Access the tool at https://console.humansecurity.com/botDefender/admin?page=integration.

  2. Locate your test device’s Visitor ID <glossary:VID>, and click the <glossary:VID> Extractor button.
    This will launch the <glossary:VID> Extractor model

  3. Insert your IP address and click Search.
    This locates all <glossary:VID>s for your APP ID.

  4. Click Select to return to the tool with the App ID and <glossary:VID> fields populated.

  5. When the App ID and <glossary:VID> are available, you can start validating denied requests.

Validating a Challenge Response

To validate a challenge response:

  1. Click Captcha in the test tool and wait for the green toast notification to confirm you are ready to continue.

  2. When the next PX token refresh occurs in your mobile application you will receive the CAPTCHA WebView.

  3. To force a token refresh simply exit and relaunch the application.

  4. Solve the challenge presented and verify that you are able to return to the application and continue using it.

Validating a Blocking Response

To validate a blocking response:

  1. Click Block in the test tool and wait for the green toast notification to confirm you are ready to continue.

  2. When the next PX token refresh occurs in your mobile application you will receive the block WebView.

  3. To force a token refresh simply relaunch the application. Once you are blocked you cannot continue.

  4. To release from blocking, exit the application, click the Clear button, and reload the application to verify that the clean token was received and the application is operating as expected.

Appendix

Configuring an Android Emulator to use Charles Proxy

To configure your Android Emulator:

  1. Set the HTTP proxy settings in the Wi-Fi menu.

  2. In the Wi-Fi menu long press the connected network, select Modify Network, and fill in the proxy settings.
    If you are running Charles Proxy on your local machine then use the local IP (not the loopback).

  3. To intercept HTTPS requests follow the guidelines at:
     https://www.charlesproxy.com/documentation/using-charles/ssl-certificates/
    In Android Nougat special permissions are required to use a certificate added to the trust store.

Deactivating the PX Mobile Sensor Remotely

In certain cases, the PX Mobile Sensor (SDK) may need to be deactivated remotely, and the app run without any sensor intervention.

This can be done based on any combination of the following parameters:

  • AppID
  • Operating System
  • Operating System Version
  • SDK Version
  • App Version

To deactivate the PX Mobile Sensor, contact your assigned focal point. Portal support to allow deactivation via the Portal will be added in the future.

Support for Full Screen on Devices with Display Cutouts

The default cutout mode is set to Never, meaning the challenge page will not render on the cutout area:

To change the behaviour to SHORT_EDGES, and have the page render on the entire display area, set the setIsCutoutFullScreen() to true.

The setIsCutoutFullScreen() method is set once before calling the start method.

Full screen on devices with display cutouts is supported on Android version P (API level 28) and above and the SDK version 1.7.0 and above.

1@Override
2public void onCreate() {
3 super.onCreate();
4 SoLoader.init(this, /* native exopackage */ false);
5
6 PXManager.getInstance()
7 .setIsCutoutFullScreen(true)
8 .start(this, "<app_id>");

The challenge page will now render over the entire display