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 to add advanced authentication to web-apps and MFA mechanisms(push notification and QR Code) and a tutorial to get started.

Integrating the CyberArk Identity SDK

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.

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 iOS sample app from GitHub and then unzip the file to your location. The sample app comes with the Identity.xcframework integrated.

Step 3: Adding Identity.xcframework into your Xcode project

In order to add the Identity.xcframework to your Xcode project follow the below steps:

  1. Create an Xcode project
  2. Right-click on the project explorer, and then select Add Files to your project
  3. Browse to locate the SDK package(IdentityIntegrationApp > IdentitySDK > Identity.xcframework) which you have downloaded as part of sample app, and then click Add
  4. Check the option Copy items if needed, and then click Finish

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>
    <key>loginurl</key>
    <string>{your_loginurl}</string>
    <key>widgetid</key>
    <string>{your_widgetid}</string>
    <key>mfatenanturl</key>
    <string>{your_mfatenanturl}</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.

loginurl

widgetid

Configured widget in the tenant portal

mfatenanturl

Step 6: Add the URL Scheme to the project

After creating the plist file, define a callback (redirect URL scheme) in the app, which helps the app to exchange the authorization codes for access tokens. To add the callback URL scheme, refer the following steps:

  1. Goto Xcode, select the root project >> target >> Info
  2. Expand the URL Types section and set the Identifier value to $(PRODUCT_BUNDLE_IDENTIFIER) and URL Scheme to unique URL scheme with the desired name.

Authorization work flow using CyberArk Identity SDK features with examples.

Start the authorization flow

First, the authentication client must have a configuration (above section) to interact with the CyberArk Identity Server. The iOS app redirects the user to CyberArk Identity for authentication. To request authorization, the iOS app must redirect the user (from browser) to make a secure HTTP call to the authorization endpoint.

Step 1: Import the CyberArk Identity SDK

import Identity

Step 2: Use the below code snippet to configure the account.

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: Define a callback to receive the access token as a result of step 2 on successful authentication.

//This method should be called either in viewDidLoad() or 
//anywhere else before calling the login method in the step 2.
func addObserver(){
        CyberArkAuthProvider.didReceiveAccessToken = { (status, message, response) in
            if status {
                DispatchQueue.main.async {
                    self.dismiss(animated: true) {
                        self.save(response: response)
                                    // Navigate to the required screen.
                      }
                }
            } else {
              // Handle errors here if any.
            }
        }
    }

Step 4: Save the access and refresh tokens to secure storage (for example : Keychain)

Save the access and refresh tokens to a secured storage, one of the best ways is Keychain Sharing. To implement Keychain Sharing, refer the following steps:

  1. Goto Xcode, select the root project >> target >> Signing & Capabilities
  2. Click on + Capability  and double click on Keychain sharing option to add the capability to the project if it is not added.
  3. Use the following code snippet to store the tokens to the Keychain.
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: Handle the response returned during the Login

It is required to handle the response received from the internal browser(Safari Controller), there should be a call back to handle the response in the app when the user is redirected to app.

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)
    }

Adding MFA

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

  • Authentication with QR Code
  • Authentication with Push notification

Authentication with QR Code

Authenticating with a QR code requires a username and password, and to leverage the QR code ability the mobile device must be enrolled. Use the below code snippet to enroll the device.

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 to receive the enrolment response

The following observer code snippet allows the app to get the response from the CyberArk Identity enrolment 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)   
                    }
                }
            }
        }
    }