Search code examples
iosswiftwkwebviewautofill

Password AutoFill: WKWebView doesn't present "Save Password" alert


I have to authenticate to my app with a web login page using WKWebView and to simplify the user experience the Password Autofill feature is added with all required steps:

  1. Set up my app’s associated domains. (https://developer.apple.com/documentation/xcode/supporting-associated-domains)
  2. Set the correct AutoFill types to user/password text fields. (https://developer.apple.com/documentation/security/password_autofill/enabling_password_autofill_on_an_html_input_element)

AutoFill works correctly and suggests username/password variants from iCloud Keychain in the QuickType bar. But it doesn’t show “Save password” alert as Safari does with the same login page.

How to enable or implement the saving password alert for WKWebView?


Solution

  • You can trigger the saving password alert manually by adding/removing correct configured UITextField in your view hierarchy:

    func showSavePassword(view: UIView, username: String, password: String) {
      let usernameInput = UITextField(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
      usernameInput.textContentType = .username
      usernameInput.text = username
      view.addSubview(usernameInput)
      
      let passwordInput = UITextField(frame: CGRect(x: 0, y: 0, width: 1, height: 1))
      passwordInput.textContentType = .password
      passwordInput.text = password
      view.addSubview(passwordInput)
      
      passwordInput.becomeFirstResponder()
      usernameInput.removeFromSuperview()
      passwordInput.removeFromSuperview()
    }
    

    To make it work automatically you can implement checking for username/password parameters in httpBody for each new request with WKNavigationDelegate:

    extension URLRequest {
      // Parse parameters
      var httpBodyParams: [String : String?]? {
        guard let httpBody,
              let text = String(data: httpBody, encoding: .utf8),
              let components = URLComponents(string: "?" + text), // Add '?' to make a valid url
              let queryItems = components.queryItems
        else {
          return nil
        }
        
        return queryItems
          .map { [$0.name : $0.value] }
          .reduce([:], { $0.merging($1) { _, new in new } })
      }
    }
    
    extension ViewController: WKNavigationDelegate {
      
      func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction) async -> WKNavigationActionPolicy {
        if let params = navigationAction.request.httpBodyParams,
           let username = params["username"] as? String,
           let password = params["password"] as? String
        {
          showSavePassword(view: view, username: username, password: password)
        }
        return .allow
      }
    }