CyberArk Identity SDK for iOS

This topic describes how to integrate CyberArk Identity multi-factor authentication with your iOS mobile app.

Overview

The CyberArk Identity SDK for iOS provides a high-performance framework to help you quickly and easily integrate CyberArk's Multi-factor Authentication (MFA) services within your own mobile app. Using the CyberArk Identity SDK, you can integrate a rich set of authentication factors, such as QR Code authentication and push authentication, to provide users with a seamless and secure end user experience.

Before you begin

Make sure you have the following before you integrate the CyberArk Identity SDk:

About this guide

This guide provides resources for integrating the CyberArk Identity SDK. The instructions help you leverage CyberArk's MFA platform. It also includes a demo app (iOS SDK demo) that illustrates how authorization works when a user scans the QR Code with a registered app, and a tutorial to get you started.

Integrate the CyberArk Identity SDK

This section provides instructions on how to set up your environment before referencing the SDK from Xcode.

Step 1: Create an OAuth 2.0 Client

If you developed a public or confidential app to access CyberArk Identity services on behalf of an end-user, you need to create an OAuth 2.0 Client. Create an OAuth 2.0 Client connection in the Admin Portal to authorize access.

For instructions on creating an OAuth 2.0 Client application in the Admin Portal, refer to
Authorization (Auth) Code Flow with PKCE.

📘

For public apps, such as native apps, the Authorization Code Flow with PKCE is recommended. To do this, select List (Apps > Web Apps > OAuth2 Client > General Usage > List) and add Allowed Clients in the Admin Portal.

Step 2: Download the CyberArk Identity SDK

Download the SDK zip for iOS from GitHub and then unzip the file into your current working directory. The SDK package includes Identity.xcframework, a demo app, and two instances of the iOS simulator.

Step 3: Add Identity.xcframework into your Xcode project

In your Xcode project, add the Identity.xcframework. To copy the framework, refer to the following steps:

  1. Start Xcode on a Mac computer.
  2. Create a project, for example Sample App. (Skip this step if you already have an existing project.)
  3. Create the IdentitFramework folder, if preferred.
  4. Right-click the project explorer, and then select Add Files to your project.
  5. Browse to locate the SDK package you downloaded, and then click Add.
  6. Select Destination: Copy items if needed, and then click Finish.
    The SDK package is then added to your project.

Step 4: Embed Identity.xcframework into the project target

Make sure that the framework is embedded into your app’s binary. To embed Identity.xcframework, refer to the following steps:

  1. In Xcode, click the project root to navigate to your project settings.
  2. Make sure that your target is selected, and that the General tab is open.
  3. Select Embed & Sign for Frameworks.Libraries and Embedded.

Step 5: Create a plist file

To customize your app with CyberArk Identity SDK features, create a custom plist file with the name IdentityConfiguration.plist. The plist file contains configuration information that is essential to receive a callback from CyberArk Identity. Copy the XML snippet below and configure it with your account information:

<plist version="1.0">
<dict>
    <key>clientid</key>
    <string>{YOUR_CLIENT_ID}</string>
    <key>domainoauth</key>
    <string>{YOUR_TENANT_URL}</string>
    <key>systemurl</key>
    <string>{YOUR_SYSTEM_URL}</string>
    <key>applicationid</key>
    <string>{YOUR_APPLICATION_ID}</string>
    <key>redirecturi</key>
    <string>{URLSCHEME}://{bundleidentifier}</string>
    <key>scope</key>
    <string>{YOUR_SCOPE}</string>
    <key>threshold</key>
    <integer>{YOUR_THRESHOLD}</integer>
    <key>responsetype</key>
    <string>code</string>
</dict>
</plist>

Parameters

Description

clientid

The client ID of your app. This is provided when you register your app in the Admin Portal.

domainoauth

The authorization server where your tenant is hosted.

systemurl

Your tenant URL. This is provided when you register your iOS app in the Admin Portal.

applicationid

A unique key used to build the OAuth2 endpoint URL.

redirecturi

The URL that you register when OAuth 2.0 is added in the Admin Portal. In other words, this is the same redirect URI that your app uses when requesting the auth code.

scope

The specific scopes that your app requests in the authorization flow.

threshold

By default, the threshold value is configured to 60.

responsetype

The type of response requested from the authorization server. This must be set to code for authorization code flow.

Step 6: Add the URI Scheme to your project

After you create the plist file, register a callback (redirect URI) scheme for the SDK. This helps your app handle the redirects to CyberArk Identity on an iOS device that exchanges authorization codes for access tokens. To add the callback URI scheme, refer to the following steps:

  1. In Xcode, click the project root to navigate to your project settings.
  2. Select the Info tab.
  3. Navigate to the URL Types section.
  4. Expand the URL Types section and add a URL scheme.
    For example, set the Identifier value to $(PRODUCT_BUNDLE_IDENTIFIER) and URL Schemes to ciamsdkdemo.

The following section describes how authorization works using a set of SDK features with examples.

Start the authorization flow

The authorization flow starts with the client app redirecting the user in the Safari browser to CyberArk Identity for authorization. The following steps illustrate how to start the authorization flow.

Step 1: Import the CyberArk Identity SDK

To programmatically import the CyberArk Identity SDK into your project, navigate to ViewController.swift in your project and insert an import statement.

// ViewController.swift 
Import identity

Step 2: : Create an account object of CyberArkAuthProvider class

To start the login process, create an object account type to call the CyberArkAuthProvider.login() method.

// ViewController.swift
         guard let config = plistValues(bundle: Bundle.main) else { return }
            //CyberarkAccount
            guard let account =  CyberArkAuthProvider.webAuth()?
                    .set(clientId: config.clientId)
                    .set(domain: config.domain)
                    .set(redirectUri: config.redirectUri)
                    .set(applicationID: config.applicationID)
                    .set(presentingViewController: self)
                    .setCustomParam(key: "", value: "")
                    .set(scope: config.scope)
                    .set(webType: .sfsafari)
                    .set(systemURL: config.systemurl)
                    .build() else { return }
            CyberArkAuthProvider.login(account: account)

Step 3: Declare an observer instance

Declare an observer instance to receive the access token.

func addObserver(){
        CyberArkAuthProvider.didReceiveAccessToken = { (status, message, response) in
            if status {
                DispatchQueue.main.async {
                    self.dismiss(animated: true) {
                        self.save(response: response)
             // this navigates to home screen
                      }
                }

            } else {

              // Handle errors here
            }
        }
    }

Step 4: Save the access and refresh tokens

To save the access or refresh tokens sent by the observer, you can use Keychain Sharing. To implement Keychain Sharing, refer to the following:

  1. Click the project to navigate to your project settings.
  2. Click Signing & Capabilities > Capability > Keychain Sharing > Keychain Groups.
  3. Add the bundle identifier com.cyberark.Identity.
  4. Use the following code snippet for Keychain Sharing.
func save(response: AccessToken?) {
        do {
            if let accessToken = response?.access_token {
                try KeyChainWrapper.standard.save(key: KeyChainStorageKeys.accessToken.rawValue, data: accessToken.toData() ?? Data())
            }
            if let refreshToken = response?.refresh_token {
             try KeyChainWrapper.standard.save(key: KeyChainStorageKeys.refreshToken.rawValue, data: refreshToken.toData() ?? Data())
            }
            if let expiresIn = response?.expires_in {
              let date = Date().epirationDate(with: expiresIn)
                try KeyChainWrapper.standard.save(key: KeyChainStorageKeys.access_token_expiresIn.rawValue, data: Data.init(from: date))
            }
        } catch {
           // Handle errors here
        }
    }

Step 5: Manage inter-app communications

You need to handle inter-app communications when a user is redirected. For example, you need a mechanism to respond to events for the new scene. Refer to the following code snippet:

func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {

        for context in URLContexts {
            do {
                process(with: context.url)
            } catch let error {
                //Handle error here
            }
        }
    }
func process(with url: URL) {
        CyberArkAuthProvider.resume(url: url)
    }

Add MFA

You can add an MFA experience into your mobile app. To do this, you can use one of the following two methods to verify MFA:

  • Authenticate with QR Code
  • Push notification to assert authentication

Authenticate with a QR Code

Authenticating with a QR code requires a username and password, and an authentication code generated from an enrolled mobile device. Use the code snippet below to implement the device enrollment model:

let enrollProvider = EnrollmentProvider() 
  /// To enroll the device 
    func enrollDevice() { 
         
        do { 
            guard let config = plistValues(bundle: Bundle.main) else { return } 
            guard let data = try KeyChainWrapper.standard.fetch(key: KeyChainStorageKeys.grantCode.rawValue), let code = data.toString() , let refreshTokenData = try KeyChainWrapper.standard.fetch(key: KeyChainStorageKeys.refreshToken.rawValue),let refreshToken = refreshTokenData.toString() else { 
                return 
            } 
          enrollProvider.enroll(baseURL: config.systemurl) 
        } catch  { 
        //Handle error case here 
        } 
    }

Add an observer instance

The following observer snippet allows the app to get the response from the CyberArk Identity enrollment API.

func addEnrollObserver(){
        enrollProvider.didReceiveEnrollmentApiResponse = { (result, message) in
            if result {
                       // Handle sucess case here
            }else {
                       // Handle error case
            }
            
        }
    }

Scan using a QR code

let provider = QRAuthenticationProvider()
// To Scan the QR code
    func scanQRCode() {
        provider.authenticateWithQRCode(presenter: self, completion: { [weak self] result in
            switch result {
            case .success(_): 
                //Handle success case here
            case .failure(let error):
               //Handle error case here    
            }

        })

    }

Get the Refresh token

Use the the following refresh token code snippet to obtain a new access token:

CyberArkAuthProvider.sendRefreshToken()

func addRefreshTokenObserver(){
        CyberArkAuthProvider.didReceiveRefreshToken = { (status, message, response) in
            if status {
                //Handle success case here 
              self.save(response: response)
            } else {    
                //Handle error case here
            }
        }
    }

Refer to Refresh Token for details.

Use the Keychain wrapper

Use the keychain wrapper to store the access tokens securely. Refer to the code snippet below:

func save(response: AccessToken?) {

        do {

            try KeyChainWrapper.standard.delete(key: KeyChainStorageKeys.accessToken.rawValue)

            try KeyChainWrapper.standard.delete(key: KeyChainStorageKeys.access_token_expiresIn.rawValue)

            if let accessToken = response?.access_token {

                try KeyChainWrapper.standard.save(key: KeyChainStorageKeys.accessToken.rawValue, data: accessToken.toData() ?? Data())

            }

            if let expiresIn = response?.expires_in {

                let date = Date().epirationDate(with: expiresIn)

                try KeyChainWrapper.standard.save(key: KeyChainStorageKeys.access_token_expiresIn.rawValue, data: Data.init(from: date))

            }

        } catch {
             //Handle error case here
        }

    }

Use push notifications for authentication

CyberArk Identity MFA delivers push notifications to a user’s pre-registered device using APNS (Apple Push Notification Service). Using push notifications, users can immediately allow or deny access to web applications.

To configure your app to receive push notifications for all your registered devices, refer to the following instructions:

Step 1: Enable remote notifications

  1. Click the project to navigate to your project settings.
  2. Select the Capability tab and find Background Modes.
  3. Double-click to add Background Modes to Signing & Capabilities.
  4. From Background Modes, select the Remote Notifications option.

Step 2: Configure Apple Push Notification services (APNs)

private let categoryIdentifier = "MfaNotificationCategoryId"
private let kMfaNotificationApproveActionID = "MfaNotificationApproveActionId"
private let kMfaNotificationDenyActionID = "MfaNotificationDenyActionId"

        let application = UIApplication.shared
        UNUserNotificationCenter.current().requestAuthorization(options: [
            .badge, .sound, .alert
        ]) { granted, _ in
            guard granted else { return }
            self.registerCustomActions()
        }
        application.registerForRemoteNotifications()

        private func registerCustomActions() {

        let accept = UNNotificationAction(

           identifier: kMfaNotificationApproveActionID,

           title: "Approve",options: UNNotificationActionOptions(rawValue: 0))

        let reject = UNNotificationAction(

            identifier: kMfaNotificationDenyActionID,

            title: "Deny",options: UNNotificationActionOptions(rawValue: 0))

        let category = UNNotificationCategory(

            identifier: categoryIdentifier,

            actions: [accept, reject],

            intentIdentifiers: [],options: .customDismissAction)

        UNUserNotificationCenter.current()

            .setNotificationCategories([category])

    }

Step 3: Send the device token to your backend server

func application(

        _ application: UIApplication,

        didRegisterForRemoteNotificationsWithDeviceToken

        deviceToken: Data) {

            guard let config = plistValues(bundle: Bundle.main, plistFileName: "IdentityConfiguration") else { return }

            CyberArkAuthProvider.handlePushToken(token: deviceToken, baseURL: config.domain)


        }

Step 4: Implement support for handling incoming remote notifications

func userNotificationCenter(_ center: UNUserNotificationCenter,

           didReceive response: UNNotificationResponse,

           withCompletionHandler completionHandler:

                                @escaping () -> Void) {

        notificationcompletionHandler = completionHandler

        let identity = response.notification

            .request.content.categoryIdentifier

        let userInfo = response.notification.request.content.userInfo

        guard identity == categoryIdentifier, let action = NotificationActionIdentifier(rawValue: response.actionIdentifier) else {

                      Notification.Name.handleNotification.post(userInfo: userInfo)

                       completionHandler()

                  return

              }

        switch action {

        case .accept:

            self.performChallengeby(isAccepted: true, userInfo: userInfo, withCompletionHandler: nil)

        case .reject:

            self.performChallengeby(isAccepted: false, userInfo: userInfo, withCompletionHandler: nil)            

        }

    }

}

Step 5: Handle the push notification MFA challenge

Below is the snippet that Invokes the API based on the approve or deny action.

extension AppDelegate {
    func performChallengeby(isAccepted: Bool, userInfo: [AnyHashable : Any]? = nil, withCompletionHandler completionHandler:
                            CheckNotificationResult? ){
        let userInfo = userInfo?["payload"] as! [AnyHashable: Any]
        let info = userInfo["Options"] as! [AnyHashable: Any]
        let challenge =  info["ChallengeAnswer"]
        handleChallange(isAccepted: isAccepted, challenge: challenge as! String, withCompletionHandler: completionHandler)
    }
    func handleChallange(isAccepted: Bool, challenge: String, withCompletionHandler completionHandler:
                         CheckNotificationResult?) {
        guard let config = plistValues(bundle: Bundle.main, plistFileName: "IdentityConfiguration")
        else { return }
        mfaProvider.handleMFAChallenge(isAccepted: isAccepted, challenge: challenge, baseURL: config.domain, withCompletionHandler: completionHandler)
    }
    func addMFAObserver(){
        mfaProvider.didReceiveMFAApiResponse = { (result, message) in
            if let handler = self.notificationcompletionHandler {
                handler()
            }
        }
    }
}

Configure APNS Certificates

To leverage the push notification functionality, you need to set the APNS certificate in the tenant level. The following APIs need to be invoked to set the APNS and to delete the APNS certificates.
You can also follow the tutorial to generate the APNS certificate.

Tenant Configuration:

  • ClientAppKey: Mobile.IosCustomApp.ClientAppKey
  • APNSCert : Base64 string representation of the APNS certificate of p12 format
  • APNSPass: Password associated with the APNS Certificate
  • APNSTopic: The topic or client bundleid related to client application with which the push notification needs to be sent.

Set the APNS certificate

API: {tenantUrl}/Mobile/SetApnsCertForClientApp

This API sets the APNS Developer certificate so you can send push notifications to a mobile device. The storage of the APNSCert and other details happen at a Tenant level. The APNSCert details related to ClientKey are used to send the push notification. Earlier ClientAppKeys are ignored when sending the push notification.

📘

You need to have APNSCertificate management rights to use this API.

API

Description

Tenant Configuration

ClientAppKey

The client application key is a unique identifier for the client application that needs to be registered for APNS notifications. By default, this is the latest certificate.

Mobile.IosCustomApp.ClientAppKey

APNSCert

Base64 string representation of the APNS certificate of p12 format.

{ClientAppKeyValue}_APNSCert

APNSPass

The password associated with the APNS certificate.

{ClientAppKeyValue}_APNSPass

APNSTopic

The topic or client packageId related to client application with which the push notification needs to be sent.

{ClientAppKeyValue}_APNSTopic

{
    "ClientAppKey":"bundleidentifier",
    "APNSCert":"hlsdfjflsdkjf",
    "APNSPass":"password”
    "APNSTopic":"bundleidentifier"
}

Delete the APNS certificate

API: {tenantUrl}/Mobile/DeleteApnsCertForClientApp

This API deletes the APNS Developer certificate. All of the configuration set as part of the SetApnCertForClientApp API is deleted as a result

📘

You need to have APNSCertificate management rights (Device Management Rights) to use this API.

ClientAppKey
Client key is a unique identifier for the client application and needs to be deleted in order to remove APNS notifications.

Strong biometric authentication

You can also leverage the CyberArk Identity SDK to implement MFA using biometric identification in the login process. Configuring the CyberArk Identity SDK provides an app with the ability to:

  • Invoke a strong biometric authentication dialog for users at app startup.
  • Handle strong biometric challenges for users, storing and retrieving the tokens as needed.

Invoke Biometric utility instance

Use the following code snippet to invoke a biometric dialog when the app starts up:

BiometricsAuthenticator().authenticateUser{ (response) in{ (response) in
                switch response {
                case .success(let success):
                  //Handle success case here
                case .failure(let error):
                  //Handle error case here

                    }
                }

Close the session from the browser

Use the closeSession() method to close the session as shown in the code snippet below:

func closeSession() {
        guard let config = plistValues(bundle: Bundle.main) else { return }
        guard let account =  CyberArkAuthProvider.webAuth()?
                .set(clientId: config.clientId)
                .set(domain: config.domain)
                .set(redirectUri: config.redirectUri)
                .set(applicationID: config.applicationID)
                .set(presentingViewController: self)
                .setCustomParam(key: "", value: "")
                .set(webType: .sfsafari)
                .build() else { return }
        CyberArkAuthProvider.closeSession(account: account)
      
  }

Declare a logout observer to receive the logout session result, and then clear the keychain data using KeyChainWrapper. Use the following code snippet for the logout observer:

func addLogoutObserver(){
        CyberArkAuthProvider.didReceiveLogoutResponse = { (result, message) in
            if result {
                DispatchQueue.main.async {
                    self.dismiss(animated: true) {
                    try KeyChainWrapper.standard.delete(key: KeyChainStorageKeys.accessToken.rawValue)

                     try KeyChainWrapper.standard.delete(key: KeyChainStorageKeys.grantCode.rawValue)

                     try KeyChainWrapper.standard.delete(key: KeyChainStorageKeys.refreshToken.rawValue)

                     try KeyChainWrapper.standard.delete(key: KeyChainStorageKeys.access_token_expiresIn.rawValue)   
                    }
                }
            }
        }
    }