To configure scenario-based mitigation policies for your app in the Bot Management console, you must integrate the Web Application Firewall (WAF) App Protection SDK. This topic describes how to integrate the App Protection SDK with an iOS app.
Background information
The App Protection SDK signs requests from the app client. The WAF server then verifies the request signature to identify and block malicious requests, protecting your app.
For the privacy policy of the App Protection SDK, see Web Application Firewall App Protection SDK Privacy Policy.
Limitations
The iOS SDK is available in two versions: an Identifier for Advertising (IDFA) version and a non-IDFA version. The corresponding SDK files are:
AliTigerTally_IDFA.framework
AliTigerTally_NOIDFA.framework
If your iOS project uses IDFA, we recommend that you integrate the AliTigerTally_IDFA version of the SDK. Otherwise, use the AliTigerTally_NOIDFA version.
The
initinterface can be time-consuming. For optimal protection, wait at least 2 seconds after calling theinitinterface before calling thevmpSigninterface. While this delay is not mandatory, a shorter interval may prevent the security features from taking full effect. You can adjust the interval based on your business needs.The App Protection SDK requires iOS version 9.0 or later.
Prerequisites
You have obtained the SDK for your iOS app.
To obtain the SDK, submit a ticket to contact a product technical expert.
NoteThe SDK file for an iOS app is named
tigertally-X.Y.Z-xxxx-ios.zip, where X.Y.Z is the version number. The file contains two framework files and two xcframework files.You have obtained the SDK authentication key (also known as the appkey).
After you enable Bot Management, navigate to the page. In the app list, click Obtain and Copy AppKey to get the SDK authentication key. This key is required for SDK initialization and must be included in your integration code.
NoteEach Alibaba Cloud account has a unique appkey for all domain names protected by WAF. This appkey is used for SDK integration across Android, iOS, and HarmonyOS apps.
Example authentication key:
****OpKLvM6zliu6KopyHIhmneb_****u4ekci2W8i6F9vrgpEezqAzEzj2ANrVUhvAXMwYzgY_****vc51aEQlRovkRoUhRlVsf4IzO9dZp6nN_****Wz8pk2TDLuMo4pVIQvGaxH3vrsnSQiK****.
Step 1: Create a project
In Xcode, create an iOS project using the setup wizard. The following figure shows an example project directory.
Step 2: Integrate the framework
Add the SDK file, AliTigerTally_IDFA.framework or AliTigerTally_NOIDFA.framework, to your project.
IDFA version

Non-IDFA version

Add the Captcha module SDK file, AliCaptcha.framework, to your project.

Add the resource file AliCaptcha.bundle to your project.

Step 3: Add dependency libraries
Dependency library | Required for IDFA | Required for non-IDFA |
libc++.tbd | Yes | Yes |
libresolv.9.tbd | Yes | Yes |
CoreTelephony.framework | Yes | Yes |
AdSupport.framework | Yes | No |
AppTrackingTransparency.framework | Yes | No |

Step 4: Add a linker flag
In the Other Linker Flags setting, add the -ObjC flag.
Step 5: Add integration code
1. Add header file
For the IDFA version, add the following import statement:
#import <AliTigerTally_IDFA/AliTigerTally.h>For the non-IDFA version, add the following import statement:
#import <AliTigerTally_NOIDFA/AliTigerTally.h>
2. Configure data signing
Set a custom end-user identifier for your business. This allows you to configure WAF protection policies with greater flexibility.
/** * Sets the user account. * * @param account The account information. */ - (void)setAccount:(NSString *)account;Parameters:
account: Type: NSString. A string that identifies a user. We recommend using a desensitized format.
Return value: None.
Example:
// For guest users, you can skip setAccount and initialize directly. After a user logs in, call setAccount and re-initialize. [[AliTigerTally sharedInstance] setAccount:@"testAccount"];
Initialize the SDK to perform data collection.
Each initialization collects device information once. You can call the init function again to re-initialize data collection for different business scenarios.
Initialization supports three data collection modes: full, custom privacy, and non-privacy. The non-privacy mode excludes fields related to end-user privacy, such as IDFA and IDFV.
NoteTo comply with your internal requirements, select a suitable data collection mode that ensures data integrity. More complete data improves threat detection.
// Initialization callback. Returns the status code of the interface call. typedef void (^TTInitListener)(int); /** * Initializes the SDK. * * @param appkey The key. * @param options Optional parameters. * @param onInitFinish The callback for when initialization is complete. * @return Indicates whether the initialization was successful. */ - (int)init:(NSString *)appkey collectType:(TTCollectType)type options:(NSMutableDictionary *_Nullable)options listener:(TTInitListener _Nullable)onInitFinish;Parameters:
appkey: Type: NSString. Your SDK authentication key.
collectType: Type: TTCollectType. The data collection mode. Valid values:
Field
Description
Example
TT_DEFAULT
Collects all data.
TT_DEFAULT
TT_NO_BASIC_DATA
Does not collect basic device data.
This includes device name, OS version, and screen resolution.
TT_NO_X | TT_NO_Y
(where X and Y represent the data field types to exclude)
TT_NO_UNIQUE_DATA
Does not collect unique identifier data.
This includes IDFV and IDFA.
TT_NO_EXTRA_DATA
Does not collect extended device data.
This includes connected Wi-Fi information (SSID, BSSID) and the list of nearby Wi-Fi networks.
TT_NOT_GRANTED
Does not collect any of the preceding privacy-related data.
TT_NOT_GRANTED
options: Type: NSMutableDictionary. Optional parameters for data collection. The default value is nil. The following parameters are available:
Field
Description
Example
IPv6
Specifies whether to use an IPv6 domain name to report device information.
0 (default): Uses an IPv4 domain name.
1: Uses an IPv6 domain name.
1
Intl
Specifies whether to use a domain name outside the Chinese mainland to report device information.
0 (default): Reports to the Chinese mainland.
1: Reports to regions outside the Chinese mainland.
1
CustomUrl
Sets the domain name of the data reporting server.
https://cloudauth-device.us-west-1.aliyuncs.com
CustomHost
Sets the host of the data reporting server.
cloudauth-device.us-west-1.aliyuncs.com
NoteFor most international sites, set only the Intl parameter. To report data to a specific site, set the CustomUrl and CustomHost parameters. Available sites include:
-
If
Intlis set to0, data is reported to the default site in China (Shanghai): https://cloudauth-device.cn-shanghai.aliyuncs.com -
If
Intlis set to1:-
The default site is Singapore: https://cloudauth-device.ap-southeast-1.aliyuncs.com
-
Indonesia (Jakarta): https://cloudauth-device.ap-southeast-5.aliyuncs.com
-
US (Silicon Valley): https://cloudauth-device.us-west-1.aliyuncs.com
-
Germany (Frankfurt): https://cloudauth-device.eu-central-1.aliyuncs.com
-
China (Hong Kong): https://cloudauth-device.cn-hongkong.aliyuncs.com
-
listener: Type: TTInitListener. The SDK initialization callback interface. You can use this callback to determine the status of the initialization. The default value is nil.
TTCode
Code
Description
TT_SUCCESS
0
Initialization successful.
TT_NOT_INIT
-1
The SDK is not initialized.
TT_NOT_PERMISSION
-2
Required iOS permissions have not been granted.
TT_UNKNOWN_ERROR
-3
An unknown system error occurred.
TT_NETWORK_ERROR
-4
A network error occurred.
TT_NETWORK_ERROR_EMPTY
-5
A network error occurred. The returned content is an empty string.
TT_NETWORK_ERROR_INVALID
-6
The format of the network response is invalid.
TT_PARSE_SRV_CFG_ERROR
-7
Failed to parse the server configuration.
TT_NETWORK_RET_CODE_ERROR
-8
The gateway returned a failure response.
TT_APPKEY_EMPTY
-9
The appkey is empty.
TT_PARAMS_ERROR
-10
Other parameter errors occurred.
TT_FGKEY_ERROR
-11
A key calculation error occurred.
TT_APPKEY_ERROR
-12
The SDK version does not match the appkey version.
Return value: Type: int. An error code.
0indicates success, and a negative number indicates failure.Example:
// The appkey is the authentication key assigned to you on the Alibaba Cloud console. NSString *appKey = @"xxxxxxxxxxxxxxxxxxxxx"; // Optional parameters. You can configure IPv6 and reporting from outside the Chinese mainland. NSMutableDictionary *options = [[NSMutableDictionary alloc] init]; [options setValue:@"0" forKey:@"IPv6"]; // Use IPv4. [options setValue:@"0" forKey:@"Intl"]; // Report data from within the Chinese mainland. // [options setValue:@"1" forKey:@"Intl"]; // Report data from outside the Chinese mainland. // Report data to the US (West) region. // [options setValue:@"https://cloudauth-device.us-west-1.aliyuncs.com" forKey:@"CustomUrl"]; // [options setValue:@"cloudauth-device.us-west-1.aliyuncs.com" forKey:@"CustomHost"]; // An initialization call collects device information once. You can call the init function again to collect information for different business scenarios. // Full data collection. if (0 == [[AliTigerTally sharedInstance] init:appkey collectType:TT_DEFAULT options:options listener:nil]) { NSLog(@"Initialization successful"); } else { NSLog(@"Initialization failed"); } // To collect specific types of private data, use the "|" operator to specify multiple types. TTCollectType collectPrivacy = TT_NO_BASIC_DATA | TT_NO_EXTRA_DATA; int ret = [[AliTigerTally sharedInstance] init:appkey collectType:collectPrivacy options:options listener:nil]; // Do not collect private data. int ret = [[AliTigerTally sharedInstance] init:appkey collectType:TT_NOT_GRANTED options:options listener:nil];
Data hashing.
This custom signing interface hashes the input data to generate a
whashstring, which serves as a custom signature. For POST, PUT, and PATCH requests, use the request body as input. For GET and DELETE requests, use the full URL. You must also add the generatedwhashstring to the ali_sign_whash field in the HTTP request header.// Request type: typedef NS_ENUM(NSInteger, TTRequestType) { TT_GET=0, TT_POST, TT_PUT, TT_PATCH, TT_DELETE }; /** * Hashes the custom signature data. * @param type The data type. * @param input The data to be signed. * @return The whash string. */ - (NSString *)vmpHash:(TTRequestType)type input:(NSData *)input;Parameters:
type: Type: TTTypeRequest. The data type. Valid values:
GET: GET request data.
POST: POST request data.
PUT: PUT request data.
PATCH: PATCH request data.
DELETE: DELETE request data.
input: Type: NSData. The data to be signed. Pass the body or URL based on the type.
Return value: Type: NSString. The
whashstring.Example:
// GET request NSString *url = @"https://tigertally.aliyun.com/apptest"; NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_GET input:[url dataUsingEncoding:NSUTF8StringEncoding]]; NSLog(@"whash: %@", whash); // POST request NSString *body = @"hello world"; NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_POST input:[body dataUsingEncoding:NSUTF8StringEncoding]]; NSLog(@"whash: %@", whash);
NoteCalling this interface is only required if you have enabled the custom signing option in the WAF console. If you use the default signature configuration, you can skip this step.
Data signing.
This method uses VMP technology to sign the input data and returns a
wtokenstring for request authentication./** * Signs the data. * @param input The data to be signed. * @return The wtoken string. */ - (NSString *)vmpSign:(NSData *)input;Parameters:
input: Type: NSData. The data to be signed. This is typically the entire request body or the
whashfrom custom signing.
Return value: Type: NSString. The
wtokenstring.Example:
// Default signature configured in the console (custom signing is not selected). NSString *body = @"hello world"; NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[body dataUsingEncoding:NSUTF8StringEncoding]]; NSLog(@"wtoken: %@", wtoken); // Custom signing configured in the console. // Custom signing for a POST request. NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_POST input:[body dataUsingEncoding:NSUTF8StringEncoding]]; NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]]; NSLog(@"whash: %@, wtoken: %@", whash, wtoken); // Custom signing for a GET request. NSString *url = @"https://tigertally.aliyun.com/apptest"; NSString *whash = [[AliTigerTally sharedInstance] vmpHash:TT_GET input:[url dataUsingEncoding:NSUTF8StringEncoding]]; NSString *wtoken = [[AliTigerTally sharedInstance] vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]]; NSLog(@"whash: %@, wtoken: %@", whash, wtoken);NoteWhen using custom signing, the input for the vmpSign interface must be the
whashstring generated by vmpHash. When you configure a scenario-based policy for App Protection, you must set the Custom Signing Field toali_sign_whash.When you call vmpHash to generate a
whashfor a GET request, ensure the input URL is identical to the final network request URL. Pay close attention to URL encoding, as some frameworks automatically encode characters or parameters.The input for the vmpHash interface cannot be a byte array or an empty string. If the input is a URL, it must include a path or a parameter.
When calling vmpSign, if the request body is empty (for example, in a GET request or a POST request with no body), pass
nilor the NSData representation of an empty string, such as[@"" dataUsingEncoding:NSUTF8StringEncoding].If
whashorwtokenreturns one of the following strings, it indicates an error:you must call init first: The
initfunction was not called.you must input correct data: The input data is invalid.
you must input correct type: The input type is invalid.
3. Secondary verification
Check the result.
To determine if secondary verification is needed, check the cookie and body from the response. If the header contains multiple Set-Cookie fields, merge them into a single cookie string before calling this interface.
/** * Checks whether to perform secondary verification. * * @param cookie The response cookie. * @param body The response body. * @return 0: Pass, 1: Secondary verification required. */ - (int)cptCheck:(NSString *)cookie body:(NSData *)body;Parameters:
cookie: Type: NSString. All cookies in the request response.
body: Type: NSData. The entire body of the request response.
Return value: Type: int. The decision result.
0indicates that the request passed, and1indicates that secondary verification is required.Example:
NSString *cookie = @"key1=value1;key2=value2;"; NSData *body = xxx; int recheck = [[AliTigerTally sharedInstance] cptCheck:cookie body:body]; NSLog(@"recheck: %d", recheck);
Create a slider.
If the result from cptCheck indicates a need for verification, create a slider object. The TTCaptcha object provides the
showanddismissmethods to control the slider window. Use TTOption to configure its parameters and TTDelegate to handle its status callbacks. To customize the slider window, you must provide a URL to a custom page, which can be a local HTML file or a remote page./** * Displays the slider for verification. * * @param view The parent component. * @param option The parameters. * @param detegate The callback protocol. */ - (TTCaptcha *)cptCreate:(UIView *)view option:(TTOption *)option delegate:(id<TTDelegate>)detegate; @protocol TTDelegate <NSObject> @required // Slider verification is successful. - (void)success:(TTCaptcha *)captcha data:(NSString *)data; // Slider verification failed. - (void)failed:(TTCaptcha *)captcha code:(NSString *)code; @end @interface TTOption : NSObject // Tap to cancel. @property (nonatomic, assign) BOOL cancelable; // Custom page. @property (nonatomic, strong) NSString *customUri; // Language. @property (nonatomic, strong) NSString *language; @end @interface TTCaptcha : NSObject - (instancetype)init:(UIView *)view option:(TTOption *)option delegate:(id<TTDelegate>)delegate; // Obtains the slider traceId for data analytics. - (NSString *)getTraceId; // Displays the slider. - (void)show; // Dismisses the slider. - (void)dismiss; @endParameters:
view: Type: View. The current page view.
option: Type: TTOption. The slider configuration parameters.
delegate: Type: TTDelegate. The slider status callback.
Return value: Type: TTCaptcha. The slider object.
Example:
#pragma mark - TTDelegate - (void)failed:(TTCaptcha *)captcha code:(nonnull NSString *)code { NSLog(@"captcha failed: %@", code); } - (void)success:(TTCaptcha *)captcha data:(nonnull NSString *)data { NSLog(@"captcha success: %@", data); } TTOption *option = [[TTOption alloc] init]; // option.customUri = @"ali-tt-captcha-demo-ios"; option.language = @"cn"; option.cancelable = true; TTCaptcha *captcha = [[AliTigerTally sharedInstance] cptCreate:[self view] option:option delegate:self]; [captcha show];NoteA "Verification failed" error indicates that an exception was detected during or after user interaction with the slider.
The following table describes the error codes.
1001: Verification challenge failed.
1002: A system exception occurred.
1003: An invalid parameter was specified.
1005: The verification was canceled.
8001: Failed to display the slider.
8002: An exception occurred in the slider verification data.
8003: An internal exception occurred during slider verification.
8004: A network error occurred.
Best practice
#import "DemoController.h"
#if __has_include(<AliTigerTally_NOIDFA/AliTigerTally_NOIDFA.h>)
#import <AliTigerTally_NOIDFA/AliTigerTally_NOIDFA.h>
#else
#import <AliTigerTally_IDFA/AliTigerTally_IDFA.h>
#endif
@interface DemoController () <TTDelegate>
@end
static NSString *kAppHost = @"******";
static NSString *kAppUrl = @"******";
static NSString *kAppkey = @"******";
@implementation DemoController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self doTest];
}
- (void)doTest {
NSThread *thread = [[NSThread alloc] initWithBlock:^{
// Initialize the SDK.
NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
// [options setValue:@"1" forKey:@"Intl"]; // Configure for international reporting.
int code = [[AliTigerTally sharedInstance] init:kAppkey collectType:TT_DEFAULT options:options listener:nil];
NSLog(@"tigertally init: %d", code);
// Do not make a synchronous call immediately.
[NSThread sleepForTimeInterval:2.0];
// Generate the signature.
NSString *body = @"hello world";
NSString *whash = nil, *wtoken = nil;
// Custom signing.
whash = [[AliTigerTally sharedInstance] vmpHash:TT_Post input:[body dataUsingEncoding:NSUTF8StringEncoding]];
wtoken = [[AliTigerTally sharedInstance] vmpSign:[whash dataUsingEncoding:NSUTF8StringEncoding]];
NSLog(@"tigertally vmp: %@, %@", whash, wtoken);
// Default signing.
// wtoken = [[AliTigerTally sharedInstance] vmpSign:[body dataUsingEncoding:NSUTF8StringEncoding]];
// NSLog(@"tigertally vmp: %@", wtoken);
[self doPost:kAppUrl host:kAppHost whash:whash wtoken:wtoken body:[body dataUsingEncoding:NSUTF8StringEncoding] callback:^(NSInteger code, NSString * _Nonnull cookie, NSData * _Nonnull body) {
int check = [[AliTigerTally sharedInstance] cptCheck:cookie body:body];
NSLog(@"captcha result:%d", check);
if (check == 0) return;
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self doShow];
}];
}];
}];
[thread start];
}
- (void)doShow {
NSLog(@"captcha show");
TTOption *option = [[TTOption alloc] init];
// option.customUri = @"ali-tt-captcha-demo-ios";
option.language = @"cn";
option.cancelable = true;
TTCaptcha *captcha = [[AliTigerTally sharedInstance] cptCreate:[self view] option:option delegate:self];
[captcha show];
}
- (void)doPost:(NSString *)url host:(NSString *)host whash:(NSString *)whash wtoken:(NSString *)wtoken body:(NSData *)body callback:(void(^)(NSInteger code, NSString* cookie, NSData *body))callback {
NSLog(@"start reqeust post");
NSURL* requestUrl = [NSURL URLWithString: url];
NSData* requestBody = body;
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:requestUrl cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
[request setValue: @"text/x-markdown" forHTTPHeaderField: @"Content-Type"];
[request setValue: host forHTTPHeaderField: @"HOST"];
[request setValue: wtoken forHTTPHeaderField:@"wToken"];
if (whash) {
[request setValue:whash forHTTPHeaderField:@"ali_sign_whash"];
}
request.HTTPMethod = @"post";
request.HTTPBody = requestBody;
NSURLSessionDataTask* dataTask = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"tiger tally sign failed: %@", [error description]);
callback(-1, nil, [[error description] dataUsingEncoding:NSUTF8StringEncoding]);
return;
}
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*) response;
NSInteger code = httpResponse.statusCode;
NSString* body = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSMutableString* cookies = [[NSMutableString alloc] init];
for (NSHTTPCookie* cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
if ([url containsString:[cookie domain]]) {
NSLog(@"domain:%@, path: %@, name:%@, value: %@", [cookie domain], [cookie path], [cookie name], [cookie value]);
[cookies appendFormat: @"%@=%@;", [cookie name], [cookie value]];
}
}
NSLog(@"reponse code: %ld", code);
NSLog(@"reponse cookie: %@", cookies);
NSLog(@"reponse body: %@", body ? (body.length > 100 ? [body substringToIndex:100]:body) : @"");
callback(code, cookies, data);
}];
[dataTask resume];
}
#pragma mark - TTDelegate
- (void)failed:(TTCaptcha *)captcha code:(nonnull NSString *)code {
NSLog(@"captcha failed: %@", code);
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:code preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:true completion:nil];
}
- (void)success:(TTCaptcha *)captcha data:(nonnull NSString *)data {
NSLog(@"captcha success: %@", data);
UIAlertController *alert = [UIAlertController alertControllerWithTitle:nil message:data preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alert animated:true completion:nil];
}
@end