Yandex Cloud
Search
Contact UsGet started
  • Blog
  • Pricing
  • Documentation
  • All Services
  • System Status
    • Featured
    • Infrastructure & Network
    • Data Platform
    • Containers
    • Developer tools
    • Serverless
    • Security
    • Monitoring & Resources
    • ML & AI
    • Business tools
  • All Solutions
    • By industry
    • By use case
    • Economics and Pricing
    • Security
    • Technical Support
    • Customer Stories
    • Gateway to Russia
    • Cloud for Startups
    • Education and Science
  • Blog
  • Pricing
  • Documentation
Yandex project
© 2025 Yandex.Cloud LLC
Tutorials
    • All tutorials
    • Differentiation of access permissions for user groups
    • Creating an L7 load balancer with a Smart Web Security security profile through an Application Load Balancer Ingress controller
    • Centralized online publication and app protection against DDoS attacks
    • Delivering logs from a VM instance to Cloud Logging
    • Writing load balancer logs to PostgreSQL
    • Secure storage of GitLab CI passwords as Yandex Lockbox secrets
    • Service account with an OS Login profile for VM management via Ansible
    • Transferring logs from Container Optimized Image to Cloud Logging
    • Adding an HTML page to work with SmartCaptcha
    • Creating an L7 load balancer with a security profile
    • Alert settings in Monitoring
    • Exporting audit logs to MaxPatrol SIEM
    • Exporting audit logs to SIEM Splunk systems
    • Uploading audit logs to ArcSight SIEM
      • CAPTCHA in Android apps
      • Invisible CAPTCHA in Android apps
      • CAPTCHA in an Android app on Flutter
      • CAPTCHA in iOS apps
    • Server-side encryption for an Object Storage bucket
    • Encrypting secrets in Hashicorp Terraform
    • Managing KMS keys with Hashicorp Terraform
    • Auto Unseal in Hashicorp Vault

In this article:

  • Getting started
  • Configure your website's JS part
  • Configure your website's native part
  • challengeDidAppear method for invisible CAPTCHA
  • challengeDidDisappear method for invisible CAPTCHA
  • Sample implementation in Swift using https://smartcaptcha.yandexcloud.net/webview
  1. Security
  2. SmartCaptcha use cases
  3. CAPTCHA in iOS apps

Yandex SmartCaptcha in iOS apps

Written by
Yandex Cloud
Updated at January 23, 2025
  • Getting started
  • Configure your website's JS part
  • Configure your website's native part
    • challengeDidAppear method for invisible CAPTCHA
    • challengeDidDisappear method for invisible CAPTCHA
  • Sample implementation in Swift using https://smartcaptcha.yandexcloud.net/webview

To embed SmartCaptcha in an iOS app:

  1. Configure your website's JS part.
  2. Configure your website's native part.

Getting startedGetting started

  1. Add HTML code to work with SmartCaptcha (or use a ready-made https://smartcaptcha.yandexcloud.net/webview).
  2. Create a CAPTCHA by following this guide.
  3. Retrieve the CAPTCHA keys. Copy the Client key and Server key field values from the Overview tab of the CAPTCHA you created. You will need the Client key to load the page with CAPTCHA, and the Server key to get the CAPTCHA test results.

Configure your website's JS partConfigure your website's JS part

If not using https://smartcaptcha.yandexcloud.net/webview, follow these steps:

  1. Add a SmartCaptcha widget to the web page.

  2. Create a method to communicate with the app's native part:

    function sendIos(...args) {
      if (args.length == 0) {
        return;
      }
      const message = {
        method: args[0],
        data: args[1] !== undefined ? args[1] : ""
      };
    
      // Check for a call from WKWebView.
      if (window.webkit) {
        window.webkit.messageHandlers.NativeClient.postMessage(message);
      }
    }
    

    With the following message format:

    {
      method: "captchaDidFinish" | "challengeDidAppear" | "challengeDidDisappear"
      data: "tokenName" | ""
    }
    

    The method returns the following:

    • success: Successful user validation.
    • challenge-visible: Opening the challenge pop-up window.
    • challenge-hidden: Closing the challenge pop-up window.

Configure your website's native partConfigure your website's native part

  1. In WKUserContentController, register a handler named WKScriptMessageHandler for the NativeClient key.

  2. Implement the following method in the handler:

    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
      guard let jsData = message.body as? [String: String] else { return }
        guard let methodName = jsData["method"] else { return }
          doSomething(name: methodName, params: jsData["data"])
    }
    
  3. Once you get the token from the captchaDidFinish method, send a POST request to the server to validate https://smartcaptcha.yandexcloud.net/validate providing parameters in the x-www-form-urlencoded format:

    • secret: Server key.
    • token: One-time token received after passing the check.
    • ip: IP address of the user that originated the request to validate the token. This is an optional parameter, but we ask you to provide the user IP when making requests. This helps improve SmartCaptcha performance.

challengeDidAppear method for invisible CAPTCHAchallengeDidAppear method for invisible CAPTCHA

CAPTCHA will not be shown in the HTML code of the page if called with the invisible parameter. Make sure WKWebView is loaded but not available to the user until the challengeDidAppear method is called. Here is one of the ways to ensure this:

UIApplication.shared.windows.first?.addSubview(webControllerView)

If the validation results in captchaDidFinish, remove webControllerView from the hierarchy. If the result does not contain captchaDidFinish, move webControllerView to the hierarchy for displaying to the user.

challengeDidDisappear method for invisible CAPTCHAchallengeDidDisappear method for invisible CAPTCHA

If CAPTCHA is swiped off the screen, it cannot be recovered by the user unassisted. In this case, call the WKWebView content reload on the challengeDidDisappear event:

webControllerView.reload()

Sample implementation in Swift using https://smartcaptcha.yandexcloud.net/webviewSample implementation in Swift using https://smartcaptcha.yandexcloud.net/webview

This section describes the steps to follow to create an app with a CAPTCHA for iOS. See the example of a ready-to-use app with all components configured: Yandex SmartCaptcha for iOS.

  1. Create a class to store WKWebView:

    final class WebNativeBridge: NSObject {
    
      private(set) var view: WKWebView?
      private var userContentController = WKUserContentController()
    
      func load(_ request: URLRequest?) {
        guard let request = request else { return }
        view?.load(request)
      }
    
      func reload() {
        view?.reload()
      }
    
      private func close() {
        view?.removeFromSuperview()
      }
    
      private func getConfiguration() -> WKWebViewConfiguration {
        let configuration = WKWebViewConfiguration()
        configuration.userContentController = userContentController
        return configuration
      }
    }
    
  2. Add a property to store the WKUserContentController handler:

    private var handlers = [String: WebContentHandlerBase]()
    
      func setup(handlers: [String: WebContentHandlerBase]) {
        handlers.forEach { userContentController.add($1, name: $0) }
        view = WKWebView(frame: .zero, configuration: getConfiguration())
      }
    
  3. Create a handler implementation for the SmartCaptcha page methods:

    class WebContentHandlerBase: NSObject, WKScriptMessageHandler {
      var handlerName: String { "" }
      func execMethod(name: String, params: Any?...) {}
    
      func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        guard let jsData = message.body as? [String: String] else { return }
        guard let methodName = jsData["method"] else { return }
        execMethod(name: methodName, params: jsData["data"])
      }
    }
    
    final class CaptchaHandler: WebContentHandlerBase {
      private enum Methods: String {
        case captchaDidFinish
        case challengeDidAppear
        case challengeDidDisappear
      }
    
      override var handlerName: String {
        "NativeClient"
      }
    
      weak var delegate: CaptchaHandlerDelegate?
      private var validator: CaptchaValidatorProtocol
    
      init(_ validator) {
        self.validator = validator
      }
    
      override func execMethod(name: String, params: Any?...) {
        guard let method = Methods(rawValue: name) else { return }
        switch method {
          case .captchaDidFinish:
            guard let token = params.first as? String else { return }
            onSuccess(token: token)
          case .challengeDidDisappear:
            onChallengeHide()
          case .challengeDidAppear:
            onChallengeVisible()
        }
      }
    
      private func onSuccess(token: String) {
        validator.validateCaptcha(token: token) { result in
          DispatchQueue.main.async {
            switch result {
              case .success(_):
                self.delegate?.onSuccess()
              case .failure(let err):
                self.delegate?.onError(err)
            }
          }
        }
      }
    
      private func onChallengeVisible() {
        delegate?.onShow()
      }
    
      private func onChallengeHide() {
        delegate?.onHide()
      }
    }
    
  4. Create a class to validate the token from SmartCaptcha

    final class CaptchaValidator: CaptchaValidatorProtocol {
      private var host: String
      private var secret: String
      private var session: URLSession
    
      init(host: String, secret: String) {
        self.host = host
        self.secret = secret
        session = URLSession(configuration: .default)
      }
    
      func validateCaptcha(token: String, callback: @escaping (Result<String, Error>) -> Void) {
        guard let url = URL(string: host),
          var components = URLComponents(url: url, resolvingAgainstBaseURL: true) else
        { return }
        components.queryItems = [
          URLQueryItem(name: "secret", value: secret),
          URLQueryItem(name: "token", value: token),
          URLQueryItem(name: "ip", value: getIPAddress()),
        ]
        let task = session.dataTask(with: URLRequest(url: components.url!)) { data, response, error in
          guard let code = (response as? HTTPURLResponse)?.statusCode, code == 200 else { return }
          guard let data = data,
            let result = try? JSONDecoder().decode(YACValidationResponse.self, from: data) else { return }
          if result.status == "ok" {
            callback(.success("ok"))
          } else {
            callback(.failure(NSError(domain: result.message ?? "", code: code)))
          }
        }
        task.resume()
      }
    
      private func getIPAddress() -> String {
        var address: String = ""
        var ifaddr: UnsafeMutablePointer<ifaddrs>? = nil
        if getifaddrs(&ifaddr) == 0 {
          var ptr = ifaddr
          while ptr != nil {
            defer {
              ptr = ptr?.pointee.ifa_next
            }
    
            let interface = ptr?.pointee
            let addrFamily = interface?.ifa_addr.pointee.sa_family
            if addrFamily == UInt8(AF_INET) {
    
              if String(cString: (interface?.ifa_name)!) == "en0" {
                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)
                print(address)
              }
            }
          }
          freeifaddrs(ifaddr)
        }
        return address
      }
    }
    

Was the article helpful?

Previous
CAPTCHA in an Android app on Flutter
Next
Which encryption method should I choose?
Yandex project
© 2025 Yandex.Cloud LLC