iOSで電話番号認証を行う方法

# はじめに

[GameWith Advent Calendar 2018](https://qiita.com/advent-calendar/2018/gamewith) 21日目担当のkyamです。
初めはiOSエンジニアのキャリアを考えるという壮大なテーマを過去に自分が担当したプロジェクトを紹介しつつ書いていたのですが、長すぎるかつ自分語りが多すぎて客観的にこれどうなんだろうとなり、とりあえず下書きに保存しました。

というわけで今回は全然テーマが変わりまして<b>iOSで電話番号認証を行う方法</b>になります。
最近よく電話番号を入力し、iPhoneに送られた通知から確認コードを取得し、それをアプリ側で入力することでユーザー認証を済ませるというケースを見かけますが、過去に自分が担当したプロジェクトでは実装したことがなく少し気になったのと触る機会があったので調べてみて簡単に検証してみました。

## Firebase Auth

みんな大好きFirebaseです。
`Firebase Authentication`の中に電話番号の項目があり、どうやらFirebaseは電話番号での認証も対応しているようです。

<img width="400" alt="スクリーンショット 2018-12-21 4.05.42.png" src="https://qiita-image-store.s3.amazonaws.com/0/66235/4ba1b74a-d893-dd3b-3cd3-deca619fdf82.png">

今回はFirebase Authを用いて電話番号でアプリ内登録を行うサンプルコードを紹介します。

## 実装方法

準備としてCocoaPodsを用いて
`pod 'Firebase/Auth'` で予めライブラリを導入しておく必要があります。

### ① 電話番号を元に確認コードを端末に送る

以下Firebaseガイドの引用です。

>
`verifyPhoneNumber:UIDelegate:completion:`を呼び出すと、Firebaseからアプリにサイレントプッシュ通知が送信されるか、ユーザーに対して`reCAPTCHAチャレンジ`が発行されます。
アプリが通知を受信した後、またはユーザーが`reCAPTCHAチャレンジ`を完了した後、Firebaseから認証コードを含むSMSメッセージが指定の電話番号に送信され、確認IDが補完関数に渡されます。ユーザーをログインさせるには、確認コードと確認IDの両方が必要です。

上記の内容を実際にコードで書いてみると以下のようになります。


```swift
//"+1 ???-???-???"
let phoneNumber = "+1?????????"
PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate: nil) { (verificationID, error) in
        if let error = error {
            self.showMessagePrompt(message: "ErrorDescription \(error.localizedDescription)")
            return
        }
        // 確認IDをアプリ側で保持しておく
        if let verificationID = verificationID {
            print("verificationID \(verificationID)")
            UserDefaults.standard.authVerificationID = verificationID
        }
    }
```

抑えておきたいポイントとしては、

- `+1 ???-???-???`のような形式の電話番号は`+1?????????`として渡す
  - 正しいフォーマットでないとInvalid Formatのエラーが返ります
- <b>ユーザー認証には確認コードだけでなく確認IDの両方が必要</b>であること
- <b>ユーザーが実際に入力するのは確認コードだけ</b>なので、確認IDはアプリ側で持っておく必要がある

### ② 確認コードを元に実際に認証を行う

```swift
guard let verificationID = UserDefaults.standard.authVerificationID else {
    self.showMessagePrompt(message: "NoVerificationID")
    return
}
let verificationCode = "123456"
let credential = PhoneAuthProvider.provider().credential(withVerificationID: verificationID, verificationCode: verificationCode)
Auth.auth().signInAndRetrieveData(with: credential) { (authResult, error) in
    if let error = error {
        self.showMessagePrompt(message: "ErrorDescription \(error.localizedDescription)")
        return
    }
    if let authResult = authResult {
        self.showMessagePrompt(message: "AuthResult \(authResult.user) \(String(describing: authResult.additionalUserInfo))")
    }
}
```

これだけでFirebaseAuthの認証が完了します。
初めてログインすると、新しいユーザーアカウントが作成され実際にコンソールにログインすることでも確認できます。

### reCAPTCHAチャレンジに関して

基本的にはアプリにサイレントプッシュ通知が送信されるのですが、ユーザーがアプリのバックグラウンド更新を無効にしている場合や、シミュレータでアプリをテストしている場合などは通知を受信できないためreCAPTCHAチャレンジを行うことになります。
<img width="250" alt="スクリーンショット 2018-12-21 4.48.50.png" src="https://qiita-image-store.s3.amazonaws.com/0/66235/5674f507-d93e-caad-b5c9-94fa206ce0dc.png">

### 上記のコード内の`showMessagePrompt`と`UserDefault`に関して

それぞれ簡単なExtensionを用意しています。以下参考までにどうぞ。

```swift
extension UIViewController {
    func showMessagePrompt(message: String) {
        let alertController = UIAlertController(title: message, message: nil, preferredStyle: .alert)
        let cancelAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
        alertController.addAction(cancelAction)
        (navigationController ?? self)?.present(alertController, animated: true, completion: nil)
    }
}
```

```swift

private let AuthVerificationIDKey = "authVerificationID"

extension UserDefaults {
    var authVerificationID: String? {
        set(newValue) {
            set(newValue, forKey: AuthVerificationIDKey)
            synchronize()
        } get {
            return string(forKey: AuthVerificationIDKey)
        }
    }
}
```

# 終わりに

Firebase Authを使うことで簡単に電話番号での認証が行えました。
電話番号や確認コードの入力はUIを作る必要があるのですが、アプリに合ったUIを作れると思うとそれはそれで良いなと思いました。

明日の22日目も連続して自分が担当させていただきます。
明日は少し長い内容になりますが<b>iOSエンジニアになりたい人が読むと良いかもしれない話</b>についてです。
お楽しみに！

# 参考
[Firebase Authentication](https://firebase.google.com/docs/auth/?hl=ja)
[ReCaPTCHAとは](https://ja.wikipedia.org/wiki/ReCAPTCHA)