Skip to content

Configure mTLS for iOS

Mutual Transport Layer Security (mTLS) is a security mechanism that extends standard TLS by requiring both the server and the client device to authenticate each other when establishing a connection.

Use this feature when your infrastructure requires client certificate authentication in addition to standard server certificate validation.

To establish an mTLS connection, the feature must be configured on both the server and the client.

This article covers only the client-side configuration. To configure mTLS on the server side, follow the Web Service mTLS guide.

Preparing Client Certificate

The Document Reader SDK does not generate, import, or store the client certificate. The host application must prepare SecIdentity, which contains a certificate and its private key.

The host application manages the certificate lifecycle. It can receive certificate material from a backend, mobile device management (MDM), a provisioning service, or another secure channel. Then, the host application creates or imports SecIdentity and stores it in Keychain.

For mTLS, the Document Reader SDK requires URLCredential created from SecIdentity.

import Foundation
import Security

func makeIdentityFromP12(base64: String, password: String) -> SecIdentity? {
    guard let p12Data = Data(base64Encoded: base64) else {
        return nil
    }

    let options: [String: Any] = [
        kSecImportExportPassphrase as String: password
    ]

    var importedItems: CFArray?
    let status = SecPKCS12Import(p12Data as CFData, options as CFDictionary, &importedItems)

    guard status == errSecSuccess,
            let items = importedItems as? [[String: Any]],
            let identity = items.first?[kSecImportItemIdentity as String] as? SecIdentity else {
        return nil
    }

    return identity
}

To load the client identity from Keychain, use the query that matches how the identity was saved by the host application:

import Security

func loadClientIdentityFromKeychain() -> SecIdentity? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassIdentity,
        kSecAttrLabel as String: "client-certificate",
        kSecReturnRef as String: true,
        kSecMatchLimit as String: kSecMatchLimitOne
    ]

    var item: CFTypeRef?
    let status = SecItemCopyMatching(query as CFDictionary, &item)

    guard status == errSecSuccess else { return nil }

    return item as? SecIdentity
}

Configuring mTLS

The host application must provide the Document Reader SDK with a ready-to-use URLCredential for the client certificate authentication challenge.

Configure authenticationChallengeDelegate before starting SDK operations that can perform network requests:

import UIKit
import DocumentReader
import Security

final class ViewController: UIViewController, URLAuthenticationChallengeDelegate {
    func startScanning() {
      // Create OnlineProcessingConfig with proper mode
      let onlineProcessingConfig = DocReader.OnlineProcessingConfig(mode: .auto)

      // Assign authenticationChallengeDelegate
      onlineProcessingConfig.authenticationChallengeDelegate = self

      // Use OnlineProcessingConfig in scanning or recognize configuration
      let scannerConfig = DocReader.ScannerConfig(scenario: RGL_SCENARIO_FULL_AUTH, onlineProcessingConfig: onlineProcessingConfig)

      // Start scan or recognize process
      DocReader.shared.startScanner(presenter: self, config: scannerConfig) { action, results, error in
      // Handler results
      }
    }

     func credential(for challenge: URLAuthenticationChallenge) -> URLCredential? {
      guard challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodClientCertificate else {
       return nil
    }

      guard let identity = loadClientIdentityFromKeychain() else {
       return nil
      }

      return URLCredential(identity: identity, certificates: nil, persistence: .forSession)
    }

    private func loadClientIdentityFromKeychain() -> SecIdentity? {
        let query: [String: Any] = [
            kSecClass as String: kSecClassIdentity,
            kSecAttrLabel as String: "<client_certificate>",
            kSecReturnRef as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne
        ]

        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)

        guard status == errSecSuccess else { return nil }

        return item as? SecIdentity
    }
}
#import <UIKit/UIKit.h>
#import <RGLDocReader/RGLDocReader.h>
#import <Security/Security.h>

@interface MyViewController: UIViewController <RGLURLAuthenticationChallengeDelegate>

@end

@implementation MyViewController

- (void)startScanning {
  // Create OnlineProcessingConfig with proper mode
  RGLOnlineProcessingConfig *onlineProcessingConfig = [[RGLOnlineProcessingConfig alloc] initWithMode:RGLOnlineProcessingModeAuto];
  // Assign authenticationChallengeDelegate
  onlineProcessingConfig.authenticationChallengeDelegate = self;
  // Create scanner or recognize configuration
  RGLScannerConfig *scannerConfig =
    [[RGLScannerConfig alloc] initWithScenario:RGL_SCENARIO_FULL_AUTH
              onlineProcessingConfig:onlineProcessingConfig];
  // Start scan or recognize process
  [[RGLDocReader shared] startScannerFromPresenter:self
                       config:scannerConfig
                    completion:^(RGLDocReaderAction action,
                          RGLDocumentReaderResults * _Nullable results,
                          NSError * _Nullable error) {
    // Handle results
  }];

}

#pragma mark - RGLURLAuthenticationChallengeDelegate

- (NSURLCredential *)credentialForChallenge:(NSURLAuthenticationChallenge *)challenge {
  if (![challenge.protectionSpace.authenticationMethod
     isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
    return nil;
  }
  SecIdentityRef identity = [self loadClientIdentityFromKeychain];
  if (identity == NULL) {
    return nil;
  }
  NSURLCredential *credential =
    [NSURLCredential credentialWithIdentity:identity
                  certificates:nil
                  persistence:NSURLCredentialPersistenceForSession];
  CFRelease(identity);
  return credential;
}

- (SecIdentityRef _Nullable)copyClientIdentityFromKeychain {
    // Load SecIdentityRef from Keychain.
    return NULL;
}

@end

If the identity is missing, invalid, expired, or unavailable, return nil from credential(for:). The Document Reader SDK will cancel the authentication challenge, and the corresponding network request will fail.

Warning

authenticationChallengeDelegate stores the delegate as a weak reference. The host app must maintain a strong reference to the delegate object. Otherwise, the delegate may be deallocated, causing requests to an mTLS server to fail silently.

Server trust validation and certificate pinning are not affected by mTLS support. Request interception logic works independently and can be used together with mTLS.