Native - Easiest Implementation
Introduction
-
In this article we will learn how to integrate the SDK to your native iOS/Android app.
-
Highlights:
- The faster way to integrate the SDK - minimum integration points.
- The SDK intercepts your URL requests.
- Handle blocked requests.
-
Note that the Android implementation requires that your app use OkHttp which supports Interceptors.
-
Note that the iOS implementation requires that your app use
URLSession
or any third party library that based on it. -
We will cover the following topics:
- How to start the SDK
- Bot Defender integration
- How to add the SDK's HTTP headers to your URL requests.
- How to handle the blocked request.
- How to present a challenge to the user.
- How to set custom parameters (optional).
- Account Defender integration
- How to enable it.
- How to notify HUMAN's backend on outgoing URL requests from the app.
- How to set additional data (optional).
Important notice about iOS
The SDK uses a custom URL protocol in order to manipulate your URL requests. The SDK creates a new URLSession
object to load your URL requests. As a result, there are few limitations that you should be aware of before the integration:
- If your implement the
urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
in yourURLSessionDelegate
, then this function will not be called and the default OS logic will apply in your URL request. - The SDK's
URLSession
object is configured withURLSessionConfiguration.default
. Any custom configuration on your side will not apply' - The SDK does not support caching in your URL requests.
How to start the SDK
- The most important thing is to start the SDK as soon as possible in your app flow. The reason is that when your app will send an URL request to your server before the SDK was started, then the request will not include the SDK's HTTP headers. As a result, HUMAN's Enforcer could block the request and the SDK will not be able to present a challenge to the user.
The best place to start the SDK is in the:Application
'sonCreate
function on Android.AppDelegate
'sdidFinishLaunchingWithOptions
function on iOS.
- You should start the SDK on the main thread.
- The SDK is intercepting URL requests from your app. Your app should tell the SDK which domains are intercepted. Not specifying domains means that ALL domains will be intercepted. The SDK checks if the URL's domain ends with one of the domains that were set. For example: If you set
example.com
the SDK will also intercept calls towww.example.com
andapi.example.com
.
Here is an example of how it should be:
Android
import android.app.Application
import com.humansecurity.mobile_sdk.HumanSecurity
import com.humansecurity.mobile_sdk.main.policy.HSPolicy
import com.humansecurity.mobile_sdk.main.policy.HSAutomaticInterceptorType
class MainApplication: Application() {
override fun onCreate() {
super.onCreate()
startHumanSDK()
}
private fun startHumanSDK() {
try {
val policy = HSPolicy()
policy.automaticInterceptorPolicy.interceptorType = HSAutomaticInterceptorType.INTERCEPT
policy.automaticInterceptorPolicy.setInterceptedDomains(setOf("example.com"), "<APP_ID>")
HumanSecurity.start(this, "<APP_ID>", policy)
}
catch (exception: Exception) {
println("Exception: ${exception.message}")
}
}
}
import android.app.Application;
import android.util.Log;
import java.util.HashSet;
import com.humansecurity.mobile_sdk.HumanSecurity;
import com.humansecurity.mobile_sdk.main.policy.HSPolicy;
import com.humansecurity.mobile_sdk.main.policy.HSAutomaticInterceptorType;
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
startHumanSDK();
}
void startHumanSDK() {
try {
HSPolicy policy = new HSPolicy();
policy.getAutomaticInterceptorPolicy().setInterceptorType(HSAutomaticInterceptorType.INTERCEPT);
HashSet<String> domains = new HashSet<>();
domains.add("example.com");
policy.getAutomaticInterceptorPolicy().setInterceptedDomains(domains, "<APP_ID>");
HumanSecurity.INSTANCE.start(this, "<APP_ID>", policy);
}
catch (Exception exception) {
Log.e("MainApplication","Exception: " + exception.getMessage());
}
}
}
iOS
import UIKit
import HUMAN
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
startHumanSDK()
return true
}
func startHumanSDK() {
do {
let policy = HSPolicy()
policy.automaticInterceptorPolicy.interceptorType = .intercept
policy.automaticInterceptorPolicy.set(interceptedDomains: ["example.com"], forAppId: "<APP_ID>")
try HumanSecurity.start(appId: "<APP_ID>", policy: policy)
}
catch {
print("Error: \(error)")
}
}
}
@import HUMAN;
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self startHumanSDK];
return YES;
}
- (void)startHumanSDK {
HSPolicy *policy = [[HSPolicy alloc] init];
policy.automaticInterceptorPolicy.interceptorType = HSAutomaticInterceptorTypeIntercept;
[policy.automaticInterceptorPolicy setWithInterceptedDomains:[NSSet setWithObject:@"example.com"] forAppId:@"<APP_ID>"];
NSError *error = nil;
[HumanSecurity startWithAppId:@"<APP_ID>" policy:policy error:&error];
if (error != nil) {
NSLog(@"Error: %@", error);
}
}
@end
Let's talk about what we have in the code here:
- We about to start the SDK as soon as possible and on the main thread.
- We create a
HSPolicy
instance. This object is used to configure the SDK's behavior. Here, we set theHSAutomaticInterceptorPolicy/interceptorType
property toHSAutomaticInterceptorType/intercept
. This will turn on the Automatic Interception feature of the SDK. In short, this feature allows the SDK to manipulate URL requests and handle their responses by itself. We also set our server's domain. This will tell the SDK to intercept URL requests only for this domain. - We call the
HumanSecurity/start(appId:policy:)
function of the SDK. We provide the following parameters:- The
Application
instance (Android only). - Your AppID.
- The policy object that we configured.
- The
Note
In iOS, The Automatic Interception uses the
URLSessionConfiguration.default
to send your URL request. However, if your app uses a custom configuration, you should set your configuration in the policy -HSAutomaticInterceptorPolicy/urlSessionConfiguration
.If your app communicates with several servers that have different AppID, you can call the
HumanSecurity/start(appIds:policy:)
function which allow you to pass an array of AppIDs. You should specify the relevant AppID for each API call in the SDK.
Bot Defender integration
How to add the SDK’s HTTP headers to your URL requests and handle the blocked request
-
The SDK adds its HTTP headers to you URL requests automatically. There is no need to manually add those headers.
-
The SDK handles the blocked request and present a challenge to the user automatically.
-
The SDK provides a custom error response directly to your request handler.
Here is an example of how it should be:
Android
import com.humansecurity.mobile_sdk.HumanSecurity
import com.humansecurity.mobile_sdk.main.HSBotDefenderErrorType
import com.humansecurity.mobile_sdk.main.HSInterceptor
import okhttp3.OkHttpClient
import okhttp3.Request
class MyHttpClient {
private val okHttpClient: OkHttpClient = OkHttpClient.Builder()
.addInterceptor(HSInterceptor()) // SHOULD BE THE LAST INTERCEPTOR
.build()
fun sendRequest(url: String) {
try {
val request: Request = Request.Builder().url(url).build()
okHttpClient.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
response.body?.string()?.let { responseBody ->
when (HumanSecurity.BD.errorType(responseBody)) {
HSBotDefenderErrorType.REQUEST_WAS_BLOCKED -> {
println("Request was blocked")
}
else -> {
println("Unknown error")
}
}
}
}
}
}
catch (exception: Exception) {
println("Request was failed. Exception: $exception")
}
}
}
import com.humansecurity.mobile_sdk.HumanSecurity
import com.humansecurity.mobile_sdk.main.HSBotDefenderErrorType
import com.humansecurity.mobile_sdk.main.HSInterceptor
import io.ktor.client.*
import io.ktor.client.call.body
import io.ktor.client.engine.okhttp.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
class MyHttpClient {
private val httpClient: HttpClient = HttpClient(OkHttp) {
engine {
addInterceptor(HSInterceptor()) // SHOULD BE THE LAST INTERCEPTOR
}
}
suspend fun sendRequest(url: String) {
try {
val response: HttpResponse = httpClient.request(url) {}
val responseBody = response.body<String>()
when (HumanSecurity.BD.errorType(responseBody)) {
HSBotDefenderErrorType.REQUEST_WAS_BLOCKED -> {
println("Request was blocked")
}
else -> {
println("Unknown error")
}
}
} catch (exception: Exception) {
println("Request was failed. exception: $exception")
}
}
}
import com.humansecurity.mobile_sdk.HumanSecurity;
import com.humansecurity.mobile_sdk.main.HSInterceptor;
import android.util.Log;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class MyHttpClient {
private final OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(new HSInterceptor()) // SHOULD BE THE LAST INTERCEPTOR
.build();
void sendRequest(String url) {
Runnable r = () -> {
try {
Request request = new Request.Builder().url(url).build();
Response response = httpClient.newCall(request).execute();
ResponseBody responseBody = response.body();
String responseString = null;
if (responseBody != null) {
responseString = responseBody.string();
response.close();
}
if (!response.isSuccessful() && responseString != null) {
switch (HumanSecurity.INSTANCE.getBD().errorType(responseString)) {
case REQUEST_WAS_BLOCKED:
Log.i("MyInterceptor", "Request was blocked");
break;
}
}
}
catch (Exception exception) {
Log.i("MyHttpClient", "Request was failed. Exception: " + exception);
}
};
new Thread(r).start();
}
}
iOS
Using URLSession
:
import HUMAN
class MyHttpClient {
func sendUrlRequest(url: URL) {
var request = URLRequest(url: url)
// Config your request...
let dataTask = URLSession.shared.dataTask(with: request) { data, response, error in
if let error {
let errorType = HumanSecurity.BD.errorType(error: error)
switch errorType {
case .requestWasBlocked:
print("Request was blocked")
default:
break
}
}
}
dataTask.resume()
}
}
@import HUMAN;
@implementation MyHttpClient
- (void)sendUrlRequest:(NSURL *)url {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// Config your request...
NSURLSessionDataTask *dataTask = [NSURLSession.sharedSession dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error != nil) {
HSBotDefenderErrorType errorType = [HumanSecurity.BD errorTypeWithError:error];
switch (errorType) {
case HSBotDefenderErrorTypeRequestWasBlocked:
NSLog(@"Request was blocked");
break;
default:
break;
}
}
}];
[dataTask resume];
}
@end
Using Alamofire
:
import Alamofire
import HUMAN
class MyHttpClient {
func sendUrlRequest(url: URL) {
AF.request(url).response { response in
if let error = response.error?.underlyingError {
let errorType = HumanSecurity.BD.errorType(error: error)
switch errorType {
case .requestWasBlocked:
print("Request was blocked")
default:
break
}
}
}
}
}
Let's talk about what we have in the code here:
- [Android only] We configure the HTTP client to have the
HSInterceptor
. This interceptor must be the last interceptor in the list. - We send the URL request.
- We check with the SDK if the error that was received is a "blocked request error".
Please note that while your request handler is called, the SDK is presenting a challenge to the user.
What should I do when a request is blocked
-
Handle it as a failure.
Your app should handle the blocked request as a failure. However, you should consider that your app's UI will be shown again after the challenge was solved/cancelled by the user. If the request was triggered by a user's action, you should make it clear that the user may try again the same action.
How to set custom parameters (optional)
You can set custom parameters in order to configure HUMAN's backend with additional parameters.
Those parameters can be set with a dictionary (iOS) or an hash map (Android), with the key "custom_param[x]
" where [x]
is a number between 1-10
.
You should call the HSBotDefender/setCustomParameters(parameters:forAppId:)
only after the HumanSecurity/start(appId:policy:)
function was already called.
Here is an example of how it should be:
Android
import com.humansecurity.mobile_sdk.HumanSecurity
fun setCustomParametersForBotDefender() {
try {
val customParameters = HashMap<String, String>()
customParameters["custom_param1"] = "hello"
customParameters["custom_param2"] = "world"
HumanSecurity.BD.setCustomParameters(customParameters, "<APP_ID>")
}
catch (exception: Exception) {
println("Exception: ${exception.message}")
}
}
import com.humansecurity.mobile_sdk.HumanSecurity;
import java.util.HashMap;
import android.util.Log;
void setCustomParametersForBotDefender() {
try {
HashMap<String, String> customParameters = new HashMap<>();
customParameters.put("custom_param1", "hello");
customParameters.put("custom_param2", "world");
HumanSecurity.INSTANCE.getBD().setCustomParameters(customParameters, "<APP_ID>");
}
catch(Exception exception) {
Log.e("MainApplication","Exception: " + exception.getMessage());
}
}
iOS
import HUMAN
func setCustomParametersForBotDefender() {
do {
var customParameters = [String: String]()
customParameters["custom_param1"] = "hello"
customParameters["custom_param2"] = "world"
try HumanSecurity.BD.setCustomParameters(parameters: customParameters, forAppId: "<APP_ID>")
}
catch {
print("Error: \(error)")
}
}
@import HUMAN;
- (void)setCustomParametersForBotDefender {
NSMutableDictionary<NSString *, NSString *> *customParameters = [[NSMutableDictionary<NSString *, NSString *> alloc] init];
customParameters[@"custom_param1"] = @"hello";
customParameters[@"custom_param2"] = @"world";
NSError* error = nil;
[HumanSecurity.BD setCustomParametersWithParameters:customParameters forAppId:@"<APP_ID>" error:&error];
if (error != nil) {
NSLog(@"Error: %@", error);
}
}
Account Defender integration
How to enable Account Defender in your app?
In order to enable Account Defender, you should set the UserID
of your current logged-in user in the SDK.
Here is an example of how it should be:
Android
import com.humansecurity.mobile_sdk.HumanSecurity
fun onUserLoggedIn(userID: String) {
try {
HumanSecurity.AD.setUserId(userID, "<APP_ID>")
}
catch (exception: Exception) {
println("Exception: ${exception.message}")
}
}
import com.humansecurity.mobile_sdk.HumanSecurity;
import android.util.Log;
void onUserLoggedIn(String userID) {
try {
HumanSecurity.INSTANCE.getAD().setUserId(userID, "<APP_ID>");
}
catch(Exception exception) {
Log.e("MainApplication","Exception: " + exception.getMessage());
}
}
iOS
import HUMAN
func onUserLoggedIn(userID: String) {
do {
try HumanSecurity.AD.setUserId(userId: userID, forAppId: "<APP_ID>")
}
catch {
print("Error: \(error)")
}
}
@import HUMAN;
- (void)onUserLoggedIn:(NSString *)userID {
NSError* error = nil;
[HumanSecurity.AD setUserIdWithUserId:userID forAppId:@"<APP_ID>" error:&error];
if (error != nil) {
NSLog(@"Error: %@", error);
}
}
How to notify HUMAN's backend on outgoing URL requests from the app
In order to allow Account Defender protect the user's account, your app has to provide the SDK with outgoing URL requests.
While the SDK's interceptor is enabled, the SDK is collecting your outgoing URL requests automatically. There is no need to manually call the SDK's API.
How to set additional data (optional)
You can set additional data in order to configure HUMAN's backend with additional parameters.
Those parameters can be set with a dictionary (iOS) or an hash map (Android).
You should call the HSAccountDefender/setAdditionalData(parameters:forAppId:)
only after the HumanSecurity/start(appId:policy:)
function was already called.
Here is an example of how it should be:
Android
import com.humansecurity.mobile_sdk.HumanSecurity
fun setAdditionalDataForAccountDefender() {
try {
val additionalData = HashMap<String, String>()
additionalData["my_key1"] = "hello"
additionalData["my_key2"] = "world"
HumanSecurity.AD.setAdditionalData(additionalData, "<APP_ID>")
}
catch (exception: Exception) {
println("Exception: ${exception.message}")
}
}
import com.humansecurity.mobile_sdk.HumanSecurity;
import java.util.HashMap;
import android.util.Log;
void setAdditionalDataForAccountDefender() {
try {
HashMap<String, String> additionalData = new HashMap<>();
additionalData.put("my_key1", "hello");
additionalData.put("my_key2", "world");
HumanSecurity.INSTANCE.getAD().setAdditionalData(additionalData, "<APP_ID>");
}
catch(Exception exception) {
Log.e("MainApplication","Exception: " + exception.getMessage());
}
}
iOS
import HUMAN
func setAdditionalDataForAccountDefender() {
do {
var additionalData = [String: String]()
additionalData["my_key1"] = "hello"
additionalData["my_key2"] = "world"
try HumanSecurity.AD.setAdditionalData(parameters: additionalData, forAppId: "<APP_ID>")
}
catch {
print("Error: \(error)")
}
}
@import HUMAN;
- (void)setAdditionalDataForAccountDefender {
NSMutableDictionary<NSString *, NSString *> *additionalData = [[NSMutableDictionary<NSString *, NSString *> alloc] init];
additionalData[@"my_key1"] = @"hello";
additionalData[@"my_key2"] = @"world";
NSError* error = nil;
[HumanSecurity.AD setAdditionalDataWithParameters:additionalData forAppId:@"<APP_ID>" error:&error];
if (error != nil) {
NSLog(@"Error: %@", error);
}
}
Updated 5 days ago