WKWebViewにBasic認証を正しく実装する

# WKWebViewにBasic認証を正しく実装する

WKWebViewにBasic認証を実装するサンプルコードです。

<dl>
  <dt>環境</dt>
  <dd>Xcode10.1</dd>
</dl>

最終的なサンプルを見たいなら[こちら](https://qiita.com/ryotapoi/items/d3aff576e001f5fa6261#サンプル)

## WebViewの生成

WKWebViewを生成しdelegateの設定を行います。
認証に関するdelegateは `navigationDelegate` です。（WKWebViewには `uiDelegate`もあるので気をつけて。）

以下はコードで生成する例です（Storyboardでも問題なし）。

```ViewController.swift
import UIKit
import WebKit

class ViewController: UIViewController {

    var webView: WKWebView!

    override func loadView() {
        view = UIView(frame: .zero)

        let configuration = WKWebViewConfiguration()
        webView = WKWebView(frame: .zero, configuration: configuration)
        webView.translatesAutoresizingMaskIntoConstraints = false
        webView.navigationDelegate = self
        view.addSubview(webView)

        NSLayoutConstraint.activate([
            webView.topAnchor.constraint(equalTo: view.topAnchor),
            webView.leftAnchor.constraint(equalTo: view.leftAnchor),
            webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            webView.rightAnchor.constraint(equalTo: view.rightAnchor)
            ])
    }

}

extension ViewController: WKNavigationDelegate {
}
```

## 認証のdelegateを実装

認証に関するdelegateメソッドを実装します。
まずは何もしないメソッドを書きます。

```ViewController.swift
extension ViewController: WKNavigationDelegate {

    public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        switch challenge.protectionSpace.authenticationMethod {
        default:
            completionHandler(.performDefaultHandling, nil)
        }
    }

}
```


## Basic認証の処理を実装

認証に関するdelegateメソッドは、Basic認証に限らず他の認証でも呼ばれます。
**Basic認証の場合の処理と、それ以外の処理を分けて実装します。**

URLCredentialにIDとPasswordを設定して、completionHandlerを呼び出します。

```ViewController.swift
extension ViewController: WKNavigationDelegate {

    public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        switch challenge.protectionSpace.authenticationMethod {
        case NSURLAuthenticationMethodHTTPBasic:
            let credential = URLCredential(user: "user", password: "password", persistence: URLCredential.Persistence.forSession)
            completionHandler(.useCredential, credential)

        default:
            completionHandler(.performDefaultHandling, nil)
        }
    }

}
```

## サンプル

UIAlertControllerでユーザー人入力を求める場合の最終的なコードは以下になります。

IDとPasswordを入力したとき、キャンセルしたとき、そのそれぞれでcompletionHandlerを呼び出します。

```ViewController.swift
import UIKit
import WebKit

class ViewController: UIViewController {

    var webView: WKWebView!

    override func loadView() {
        view = UIView(frame: .zero)

        let configuration = WKWebViewConfiguration()
        webView = WKWebView(frame: .zero, configuration: configuration)
        webView.translatesAutoresizingMaskIntoConstraints = false
        webView.navigationDelegate = self
        view.addSubview(webView)

        NSLayoutConstraint.activate([
            webView.topAnchor.constraint(equalTo: view.topAnchor),
            webView.leftAnchor.constraint(equalTo: view.leftAnchor),
            webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            webView.rightAnchor.constraint(equalTo: view.rightAnchor)
            ])
    }

    override func viewDidLoad() {
        let request = URLRequest(url: URL(string: "https://hogehoge.com/")!)
        webView.load(request)
    }
}

extension ViewController: WKNavigationDelegate {

    public func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        switch challenge.protectionSpace.authenticationMethod {
        case NSURLAuthenticationMethodHTTPBasic:

            let alert = UIAlertController(title: "Basic認証", message: "ユーザ名とパスワードを入力してください", preferredStyle: .alert)
            alert.addTextField {
                $0.placeholder = "user"
            }
            alert.addTextField {
                $0.placeholder = "password"
                $0.isSecureTextEntry = true
            }

            let login = UIAlertAction(title: "ログイン", style: .default) { (_) in
                guard
                    let user = alert.textFields?[0].text,
                    let password = alert.textFields?[1].text
                    else {
                        completionHandler(.cancelAuthenticationChallenge, nil)
                        return
                }
                let credential = URLCredential(user: user, password: password, persistence: URLCredential.Persistence.forSession)
                completionHandler(.useCredential, credential)
            }
            let cancel = UIAlertAction(title: "キャンセル", style: .cancel) { (_) in
                completionHandler(.cancelAuthenticationChallenge, nil)
            }
            alert.addAction(login)
            alert.addAction(cancel)

            present(alert, animated: true, completion: nil)

        default:
            completionHandler(.performDefaultHandling, nil)
        }
    }

}
```