Keyri iOS SDK
System Requirements
- iOS 14+
- Swift 5+
- Apple A7 chip or newer (The A7 shipped with the iPhone 5s)
Integration
CocoaPods (opens in a new tab) is a dependency manager for Cocoa projects. For usage and installation instructions, visit their website. To integrate the Keyri iOS SDK into your Xcode project using CocoaPods, specify it in your Podfile:
pod 'keyri-pod'
The SDK can then be imported into any Swift file as follows:
import keyri-pod
Option 1 - Universal Links
To handle Universal Links (e.g., for QR login straight from the user's built-in camera app), you need to add the Associated Domains Entitlement to your App.entitlements file. To set up the entitlement in your app, open the target’s Signing & Capabilities tab in Xcode and add the Associated Domains capability, or if you already have entitlements you can modify your App.entitlements file to match this example:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:{domainName}</string>
</array>
</dict>
</plist>
This will handle all links with the following scheme:
https://{yourCompany}.onekey.to?sessionId={sessionId}
Note: Keyri will create your https://{yourCompany}.onekey.to
page
automatically once you configure it in the dashboard (opens in a new tab)
In the AppDelegate where the processing of links is declared, you need to add
handlers in the application(_:continue:restorationHandler:)
method:
Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
// Handle the deep link URL
processLink(url)
}
}
}
func processLink(url: URL) {
let appKey = "[Your appKey]" // Get this value from the Keyri Dashboard
let publicApiKey = "[Your publicApiKey]" // Get this optional value from the Keyri Dashboard for Fraud Prevention
let serviceEncryptionKey = "[Your serviceEncryptionKey]" // Get this optional value from the Keyri Developer Portal for Fraud Prevention
let publicUserId = "public-User-Id" // publicUserId is optional
let payload = "Custom payload here"
let sessionId = URLComponents(url: url, resolvingAgainstBaseURL: true)?.queryItems?.first(where: { $0.name == "sessionId" })?.value ?? ""
let keyri = KeyriInterface(appKey: appKey)
keyri.initiateQrSession(sessionId: sessionId, publicUserId: publicUserId) { sessionResult in
switch sessionResult {
case .success(let session):
// You can optionally create a custom screen and pass the session ID there. We recommend this approach for large enterprises
keyri.initializeDefaultConfirmationScreen(session: session, payload: payload, completion: completion)
// In a real world example you’d wait for user confirmation first
session.confirm(payload: payload, trustNewBrowser: trustNewBrowser, completion: completion) // or session.deny(payload, completion)
case .failure(let error):
// On error
}
}
// Or delegate link processing to Keyri SDK
keyri.processLink(url, payload, publicUserId) { result in
switch result {
case .success:
// Successfully authenticated
case .failure(let error):
// On error
}
}
}
Note: Keyri will set up the required
/.well-known/apple-app-site-association
JSON at your
https://{yourSubdomain}.onekey.to
page as required by Apple to handle
Universal Link handling. Details on this mechanism are described here:
https://developer.apple.com/documentation/Xcode/supporting-associated-domains (opens in a new tab)
Option 2 - In-App Scanner
This can be used in conjunction with Universal links or exclusively.
The Keyri SDK includes a default Scanner view, which can be invoked and
displayed as shown below. Unfortunately, due to platform limitations, we had to
keep this in UIKit for the time being but will be on the lookout for options to
convert over to SwiftUI as time goes on. The completion block is the important
piece here: we return the exact string as shown in the QR code. All you need to
do is convert to URL, and then you're free to process the response the same way
we did above (notice the process(url)
function is exactly the same in both
cases)
func openEasyKeyriAuth() {
let appKey = "[Your appKey]" // Get this value from the Keyri Dashboard
let publicApiKey = "[Your publicApiKey]" // Get this optional value from the Keyri Dashboard for Fraud Prevention
let serviceEncryptionKey = "[Your serviceEncryptionKey]" // Get this optional value from the Keyri Developer Portal for Fraud Prevention
let publicUserId = "public-User-Id" // publicUserId is optional
let payload = "Custom payload here"
let keyri = KeyriInterface(appKey: appKey)
keyri.easyKeyriAuth(payload: payload, publicUserId: publicUserId) { result in
switch result {
case .success:
// Authenticated
case .failure(let error):
// On error
}
}
}
Interacting with the API
The following methods are available to interact with the Keyri SDK API, which can be used to craft your own custom flows and leverage the SDK in different ways:
Swift
-
public init(appKey: String, publicApiKey: String? = nil, serviceEncryptionKey: String? = nil, blockEmulatorDetection: Bool? = true)
- Initialize the Keyri object with the keys retrieved from the dashboard. If you're using sendEvent() to take advantage of our fraud dashboard, the publicApiKey and serviceEncryptionKey are required -
func sendEvent(publicUserId: String = Constants.ANON_USER, eventType: EventType = .visits, success: Bool = true, completion: @escaping (Result<FingerprintResponse, Error>) -> ())
- Sends an event to our dashboard containing a device snapshot to our dashboard. The response you receive will be sent, as is, to your own backend, where you can utilize our scripts to help you decrypt. See the code sample below and https://docs.keyri.com/fraud-prevention (opens in a new tab) for more -
func KeyriInterface.easyKeyriAuth(payload: String, publicUserId: String?, completion: @escaping (Result<Void, Error>) -> ())
- call to have Keyri drive you through the entire process - we display the scanner, scan the QR code, handle user confirmation and fire off the result to the browser - all with one line of code in your app 😀 -
func KeyriInterface.initiateQrSession(sessionId: String, publicUserId: String?, completion: @escaping (Result<Session, Error>) -> Void)
- call after obtaining the sessionId from QR-code or deep link. Returns Session object with Risk attributes (needed to show confirmation screen) or Exception -
func KeyriInterface.initializeDefaultConfirmationScreen(session: Session, payload: String, completion: @escaping (Result<Void, Error>) -> ())
- to show Confirmation with default UI. Alternatively, you can implement a custom Confirmation Screen. The Default screen is built using SwiftUI, however the session object is designed to work seamlessly with UIKit as well should you prefer that route -
func KeyriInterface.login(publicUserId: String?, completion: @escaping (Result<LoginObject, Error>) -> ())
- call this method to generate object for login which includes timestampNonce, signature, publicKey and userId -
func KeyriInterface.register(publicUserId: String?, completion: @escaping (Result<RegisterObject, Error>) -> ())
- call this method to generate object for register which includes publicKey and userId -
func KeyriInterface.processLink(url: URL, payload: String, publicUserId: String?, completion: @escaping (Result<Void, Error>) -> ())
- process flow with passed uri with showing default confirmation screen. Easiest way to process session from deeplink. Returns result of authentication or error -
func Session.confirm(payload: String, trustNewBrowser: Bool = false, completion: @escaping (Error?) -> ())
- call this function if user confirms the dialog. Returns authentication result or error -
func Session.deny(payload: String, completion: @escaping (Error?) -> ())
- call if the user denies the dialog. Returns denial result or error -
func KeyriInterface.generateAssociationKey(publicUserId: String = Constants.ANON_USER, completion: @escaping (Result<P256.Signing.PublicKey, Error>) -> ())
- creates a persistent ECDSA keypair for the given public user ID (example: email address) and return public key -
func KeyriInterface.generateUserSignature(publicUserId: String = Constants.ANON_USER, data: Data, completion: @escaping (Result<P256.Signing.ECDSASignature, Error>) -> ())
- returns an ECDSA signature of the timestamp and optional customSignedData with the publicUserId's privateKey (or, if not provided, anonymous privateKey), data can be anything -
func KeyriInterface.getAssociationKey(publicUserId: String = Constants.ANON_USER, completion: @escaping (Result<P256.Signing.PublicKey?, Error>) -> ())
- returns Base64 public key for the specified publicUserId -
func KeyriInterface.removeAssociationKey(publicUserId: String, completion: @escaping (Result<Void, Error>) -> ())
- removes association public key for the specified publicUserId -
func KeyriInterface.listAssociactionKeys(completion: @escaping (Result<[String:String]?, Error>) -> ())
- returns a dictionary of "association keys" and ECDSA Base64 public keys -
func KeyriInterface.listUniqueAccounts(completion: @escaping (Result<[String:String]?, Error>) -> ())
- returns a dictionary of unique "association keys" and ECDSA Base64 public keys -
func KeyriInterface.sendEvent(publicUserId: String = Constants.ANON_USER, eventType: EventType = .visits, success: Bool = true, completion: @escaping (Result<FingerprintResponse, Error>) -> ())
- sends fingerprint event and event result for specified publicUserId's -
func KeyriInterface.createFingerprint(completion: @escaping (Result<FingerprintEventRequest>) -> ())
- creates and returns fingerprint event object
Payload can be anything (session token or a stringified JSON containing multiple items. Can include things like publicUserId, timestamp, customSignedData and ECDSA signature)
Objective-C
-
- (void)initializeKeyriWithAppKey:(NSString * _Nonnull)appKey publicApiKey:(NSString * _Nullable)publicApiKey serviceEncryptionKey:(NSString * _Nullable)serviceEncryptionKey blockEmulatorDetection:(BOOL)blockEmulatorDetection
- Initialize the Keyri object with the keys retrieved from the dashboard. If you're using sendEvent() to take advantage of our fraud dashboard, the publicApiKey and serviceEncryptionKey are required -
- (void)easyKeyriAuthWithPayload:(NSString * _Nonnull)payload publicUserId:(NSString * _Nonnull)publicUserId completion:(void (^ _Nonnull)(BOOL, NSError * _Nullable))completion
- call to have Keyri drive you through the entire process - we display the scanner, scan the QR code, handle user confirmation and fire off the result to the browser - all with one line of code in your app -
- (void)generateAssociationKeyWithPublicUserId:(NSString * _Nullable)publicUserId completion:(void (^ _Nonnull)(NSString * _Nullable, NSError * _Nullable))completion
- creates a persistent ECDSA keypair for the given public user ID (example: email address) and return public key -
- (void)generateUserSignatureWithPublicUserId:(NSString * _Nullable)publicUserId data:(NSData * _Nonnull)data completion:(void (^ _Nonnull)(NSString * _Nullable, NSError * _Nullable))completion
- returns an ECDSA signature of the timestamp and optional customSignedData with the publicUserId's privateKey (or, if not provided, anonymous privateKey), data can be anything -
- (void)listAssociationKeysWithCompletion:(void (^ _Nonnull)(NSDictionary<NSString *, NSString *> * _Nullable, NSError * _Nullable))completion
- returns a dictionary of "association keys" and ECDSA Base64 public keys -
- (void)listUniqueAccountsWithCompletion:(void (^ _Nonnull)(NSDictionary<NSString *, NSString *> * _Nullable, NSError * _Nullable))completion
- returns a dictionary of unique "association keys" and ECDSA Base64 public keys -
- (void)getAssociationKeyWithPublicUserId:(NSString * _Nullable)publicUserId completion:(void (^ _Nonnull)(NSString * _Nullable, NSError * _Nullable))completion
- returns Base64 public key for the specified publicUserId -
- (void)removeAssociationKeyWithPublicUserId:(NSString * _Nonnull)publicUserId completion:(void (^ _Nonnull)(NSError * _Nullable))completion
- removes association public key for the specified publicUserId -
- (void)initiateQrSessionWithSessionId:(NSString * _Nonnull)sessionId publicUserId:(NSString * _Nullable)publicUserId completion:(void (^ _Nonnull)(Session * _Nullable, NSError * _Nullable))completion
- call after obtaining the sessionId from QR-code or deep link. Returns Session object with Risk attributes (needed to show confirmation screen) or Exception -
- (void)loginWithPublicUserId:(NSString * _Nullable)publicUserId completion:(void (^ _Nonnull)(LoginObject * _Nullable, NSError * _Nullable))completion
- call this method to generate object for login which includes timestampNonce, signature, publicKey and userId -
- (void)registerWithPublicUserId:(NSString * _Nullable)publicUserId completion:(void (^ _Nonnull)(RegisterObject * _Nullable, NSError * _Nullable))completion
- call this method to generate object for register which includes publicKey and userId -
- (void)initializeDefaultConfirmationScreenWithSession:(Session * _Nonnull)session payload:(NSString * _Nonnull)payload completion:(void (^ _Nonnull)(BOOL, NSError * _Nullable))completion
- to show Confirmation with default UI. Alternatively, you can implement a custom Confirmation Screen. The Default screen is built using SwiftUI, however the session object is designed to work seamlessly with UIKit as well should you prefer that route -
- (void)processLinkWithUrl:(NSURL * _Nonnull)url payload:(NSString * _Nonnull)payload publicUserId:(NSString * _Nullable)publicUserId completion:(void (^ _Nonnull)(BOOL, NSError * _Nullable))completion
- process flow with passed uri with showing default confirmation screen. Easiest way to process session from deeplink. Returns result of authentication or error -
- (void)sendEventWithPublicUserId:(NSString * _Nullable)publicUserId eventType:(NSString * _Nonnull)eventType success:(BOOL)success completion:(void (^ _Nonnull)(FingerprintResponse * _Nullable, NSError * _Nullable))completion
- sends fingerprint event and event result for specified publicUserId's -
- (void)createFingerprintWithCompletion:(void (^ _Nonnull)(FingerprintEventRequest * _Nullable, NSError * _Nullable))completion
- creates and returns fingerprint event object
Session Object
The session object is returned on successful initiateQrSession
calls, and is used to handle presenting the situation to the end user and getting their confirmation to complete authentication. Below are some of the key properties and methods that can be triggered. If you are utilizing the built-in views, you are only responsible for calling the confirm/deny methods above
-
iPAddressMobile/Widget - The IP Address of both mobile device and web browser
-
riskAnalytics - if applicable
-
riskStatus - clear, warn or deny
-
riskFlagString - if RiskStatus is warn or deny, this string alerts the user to what is triggering the risk situation
-
geoData - Location data for both mobile and widget
-
mobile
-
city
-
country_code
-
browser
-
city
-
country_code
-
-
mobileTemplateResponse - Use this object to define confirmation screen UI
-
fun Session.confirm()
andfun Session.deny()
- see descriptions in Interacting with the API.
Disclaimer
We care deeply about the quality of our product and rigorously test every piece of functionality we offer. That said, every integration is different. Every app on the App Store has a different permutation of build settings, compiler flags, processor requirements, compatibility issues etc and it's impossible for us to cover all of those bases, so we strongly recommend thorough testing of your integration before shipping to production. Please feel free to email us at support@keyri.com to submit a bug report or feature request.