Additional Functionality

HYPR SDK for iOS

This document describes additional functionality available in the HYPR SDK for iOS.

Reset an App

Fully delete the App profile and all the users registered under the App profile. This will remove all features associated with the Application Profile, including (but not limited to) policies, users, devices, tokens, and associated endpoints.

HYPRUserAgent.resetUserAgent(with: .full) { (error) in
  if let error = error {  
    // Handle the error
  }
  // Full Reset was complete
}

Locally delete the App profile and all the users registered under the App profile. The device is cleared of all locally stored registrations associated with the App Profile. The App and any other associated data in the CC are unaffected.

HYPRUserAgent.resetUserAgent(with: .local) { (error) in
  if let error = error {
    // Handle the error
  }
  // Local Reset was complete
}

Custom HTTPS Headers

Customize the information sent in a network request by adding custom HTTPS headers. Custom headers can be added to registration, authentication, and deregistration requests. To send and receive headers, create an object that conforms to the HYPRCustomHeadersAdapter protocol and set that object as the CustomHeadersDelegate via HYPRUserAgent.setCustomHeadersDelegate(<delegate object here>).

Sending Custom Headers

Add custom headers to your requests:

customHeaders(forUserInfo userInfo: [String : String] = [:]) -> [AnyHashable : Any]?

Receiving Custom Headers

Process custom headers from the response:

processCustomHeaders(fromUserInfo userInfo: [String : String] = [:])

Integration

  1. Within the AppDelegate method, enable custom headers for the HYPRUserAgent.
HYPRUserAgent.setCustomHeadersDelegate(self)
  1. Create the function to set custom headers for networking requests.
func customHeaders(forUserInfo userInfo: [String : String] = [:]) -> [AnyHashable : Any]? {
        
        guard let operation = userInfo[kHYPRCustomHeadersAdapterOperation], let phase = userInfo[kHYPRCustomHeadersAdapterPhase] else {
            print("invalid userInfo sent for custom headers")
            return nil;
        }
        
        switch operation {
        case kHYPRCustomHeadersAdapterOperationRegistration:
            switch phase {
            case kHYPRCustomHeadersAdapterPhaseInit:
                return <your custom headers which is a [String:String] dictionary>
            case kHYPRCustomHeadersAdapterPhaseComplete:
                return <your custom headers which is a [String:String] dictionary>
            default:
                print("invalid phase sent for custom headers")
            }
}
  1. Create the function to process custom headers for networking responses.
func processCustomHeaders(fromUserInfo userInfo: [String : String] = [:]) {
        print("Headers Received: \(userInfo)")
        return;
    }

Example

Below is a code example where AppDelegate conforms to the HYPRCustomHeadersAdapter protocol. The function customHeaders is used for adding a custom header; processCustomHeaders is used for processing headers from the response.

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, HYPRCustomHeadersAdapter {
  internal func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Other code is here
    HYPRUserAgent.setCustomHeadersDelegate(self)
  }
  
  func customHeaders(forUserInfo userInfo: [String : String] = [:]) -> [AnyHashable : Any]? {
        
        guard let operation = userInfo[kHYPRCustomHeadersAdapterOperation], let phase = userInfo[kHYPRCustomHeadersAdapterPhase] else {
            print("invalid userInfo sent for custom headers")
            return nil;
        }
        
        switch operation {
        case kHYPRCustomHeadersAdapterOperationRegistration:
            switch phase {
            case kHYPRCustomHeadersAdapterPhaseInit:
                return <your custom headers which is a [String:String] dictionary>
            case kHYPRCustomHeadersAdapterPhaseComplete:
                return <your custom headers which is a [String:String] dictionary>
            default:
                print("invalid phase sent for custom headers")
            }
        case kHYPRCustomHeadersAdapterOperationAuthentication:
            switch phase {
            case kHYPRCustomHeadersAdapterPhaseInit:
                return <your custom headers which is a [String:String] dictionary>
            case kHYPRCustomHeadersAdapterPhaseComplete:
                return <your custom headers which is a [String:String] dictionary>
            default:
                print("invalid phase sent for custom headers")
            }
        case kHYPRCustomHeadersAdapterOperationDeregister:
            switch phase {
            case kHYPRCustomHeadersAdapterPhaseInit:
                return <your custom headers which is a [String:String] dictionary>
            default:
                print("invalid phase sent for custom headers")
            }
        case kHYPRCustomHeadersAdapterOperationOOBSetup:
            switch phase {
            case kHYPRCustomHeadersAdapterPhaseInit:
                return <your custom headers which is a [String:String] dictionary>
            default:
                print("invalid phase sent for custom headers")
            }
        case kHYPRCustomHeadersAdapterOperationOOBRegistration:
            switch phase {
            case kHYPRCustomHeadersAdapterPhaseInit:
                return <your custom headers which is a [String:String] dictionary>
            default:
                print("invalid phase sent for custom headers")
            }
        case kHYPRCustomHeadersAdapterOperationOOBAuthorization:
            switch phase {
            case kHYPRCustomHeadersAdapterPhaseInit:
                return <your custom headers which is a [String:String] dictionary>
            case kHYPRCustomHeadersAdapterPhaseComplete:
                return <your custom headers which is a [String:String] dictionary>
            default:
                print("invalid phase sent for custom headers")
            }
        default:
            print("invalid operation sent for custom headers")
        }
        return nil;
    }
    
    // For receiving HTTP Headers in HYPR SDK calls
    func processCustomHeaders(fromUserInfo userInfo: [String : String] = [:]) {
        print("Headers Received: \(userInfo)")
        return;
    }
}

Enabling SSL Pinning

By default, HYPR SDK for iOS will enforce SSL pinning. Once SSL pinning is enforced, HYPRSSLPinCredential must be supplied to HYPRUserAgentProfileConfiguration. HYPRSSLPinCredential is made up of credential content (expected to be a base64-encoded string of a SHA256 hash of a DER-encoded public key) and algorithm (key algorithm with expected values: RSA_2048, RSA_4096, ECDSA_SECP_256_R1, ECDSA_SECP_384_R1. See HYPRKeyAlgorithmType). Otherwise, SSL pinning can be disabled by setting it to false.

Here is an example using AppDelegate.swift or AppDelegate.m:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  HYPRUserAgent.setSSLPinningEnabled(true)
  let sslPinCredential = HYPRSSLPinCredential(content: "base64-encoded string of a SHA256 hash of a DER-encoded public key", algorithm: kHYPRKeyAlgorithmRsa2048)
  
  let config = HYPRUserAgentProfileConfiguration(rpAppId: "HYPRDefaultApplication",
                                                           rpServerUrl: "Place the RP URL here: i.e. https://9999-pov.hypr.com",
                                                           deviceType: "WEB or WORKSTATION"
                                                           rpSSLPinCredentials: sslPinCredential,
                                                           additionalData: nil,
                                                           authenticatorViewConfigurations: nil)
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [HYPRUserAgent setSSLPinningEnabled:YES];
  HYPRSSLPinCredential *sslPinCredential = [[HYPRSSLPinCredential alloc] initWithContent:@"base64-encoded string of a SHA256 hash of a DER-encoded public key" algorithm:kHYPRKeyAlgorithmRsa2048];
  HYPRUserAgentProfileConfiguration *config = [[HYPRUserAgentProfileConfiguration alloc] initWithRpAppId:@"HYPRDefaultApplication"                                  rpServerUrl:@"Place the RP URL here: i.e. https://9999-pov.hypr.com"              rpSSLPinCredentials:sslPinCredential                                            additionalData:nil                                                                authenticatorViewConfigurations:nil];
}

Gathering Device and App Information

  • The hyprDeviceIdentifier method returns the permanently stored universally unique identifier (UUID) used to identify the mobile device
import HyprCore

func deviceId() -> String {
	return HYPRUserAgent.hyprDeviceIdentifier()
}
  • Use the ipAddress method to return the IP address of the device
func ipAddress() -> String? {
        var address : String?
        

        // Get list of all interfaces on the local machine:
        var ifaddr : UnsafeMutablePointer<ifaddrs>?
        guard getifaddrs(&ifaddr) == 0 else { return nil }
        guard let firstAddr = ifaddr else { return nil }

        // For each interface ...
        for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
            let interface = ifptr.pointee

            // Check for IPv4 or IPv6 interface:
            let addrFamily = interface.ifa_addr.pointee.sa_family
            if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {

                // Check interface name:
                let name = String(cString: interface.ifa_name)
                if  name == "en0" {

                    // Convert interface address to a human readable string:
                    var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))
                    getnameinfo(interface.ifa_addr, socklen_t(interface.ifa_addr.pointee.sa_len),
                                &hostname, socklen_t(hostname.count),
                                nil, socklen_t(0), NI_NUMERICHOST)
                    address = String(cString: hostname)
                }
            }
        }
        freeifaddrs(ifaddr)

        return address
    }
  • The mobileOS method returns the mobile device operating system; e.g., iOS 13.3.1
func mobileOS() -> String {
  return "iOS \(UIDevice.current.systemVersion)"
}
  • The appVersion method returns the version of the application
func appVersion() -> String?{
  let bundle = Bundle(for: AppDelegate.self)
  return bundle.infoDictionary?["CFBundleShortVersionString"] as? String
}
  • deviceModel returns the iPhone Model Identifier; for example, iPhone 8 Plus will return iPhone 10.2
func deviceModel() -> String {
  var systemInfo = utsname()
  uname(&systemInfo)
  let machineMirror = Mirror(reflecting: systemInfo.machine)
  return machineMirror.children.reduce("") { identifier, element in
                                            guard let value = element.value as? Int8, value != 0 else { return identifier }
                                            return identifier + String(UnicodeScalar(UInt8(value)))
                                           }
}
  • appIdentifier returns the Bundle Identifier for the App
func appIdentifier() -> String? {
  return Bundle(for: AppDelegate.self).bundleIdentifier
}
  • Retrieve the device's location (latitude, longitude):

    Within Info.plist ensure that there is a key value pairing with the following:

    • key: Privacy – Location When In Use Usage Description
    • value:

    Here is an example using AppDelegate:

import CoreLocation
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
  var locationManager = CLLocationManager()
  var currentLocationString: String? {
    get{
      if let location = locationManager.location {
        return "{\"latitude\":"\(location.coordinate.latitude)",\"longitude\":"\(location.coordinate.longitude))"}"
      }
      else {
        return nil
      }
    }
  }

 internal func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
	locationManager.delegate = self
	let authStatus = CLLocationManager.authorizationStatus()
	if authStatus == .notDetermined {
		locationManager.requestWhenInUseAuthorization()
	}
	else if authStatus == .authorizedAlways || authStatus == .authorizedWhenInUse {
		locationManager.requestLocation()
	}
 }
}

extension AppDelegate: CLLocationManagerDelegate {
    
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        
    }
    func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        if status == .authorizedAlways || status == .authorizedWhenInUse {
            locationManager.requestLocation()
        }
    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        
    }
}