【Firebase】メールリンク認証の実装【iOS】

# はじめに
本記事では、iOSアプリにおけるFirebaseAuthを用いたメールリンク認証の実装方法を解説します。

# メールリンク認証とは
メールリンク認証とは、メールをユーザに送信し、そこに記載されたログイン用のリンクからログインさせる認証方法です。このメールリンク認証には次のようなメリットがあります。

- 登録やログインが簡単
- パスワードが必要ない
- メールアドレスの認証も同時に行える

ユーザも簡単にログインでき、デベロッパもパスワード系の機能（バリデーションや再設定など）を実装しなくて良いので、win-winな認証方法であると言えます。

# 準備
iOSおよびFirebaseコンソールでのセットアップについては省きます。まだの方は[公式チュートリアル](https://firebase.google.com/docs/ios/setup)などを参考に設定してください。

## 各種IDの取得
Dynamic Linksを利用するには、iOSアプリの「App Store ID」と「チームID」が必要になります。どちらもリリース作業時に目にするようなIDですが、リリース準備前でも取得できます。

### チームIDの取得
[Apple Developer](https://developer.apple.com/account)から「Certificates, Identifiers & Profiles」セクションを開き、アプリに紐付けたApp IDを確認します。未作成の場合は新規作成してください。Certificatesの設定などがありますが、本記事では説明を省きます。**作成したApp IDのBundle IDとXcode ProjectでのBundle IDを一致させる必要がある**ことに注意してください。
App IDの「Prefix」がチームIDになります。
<img width="250" alt="team_id.png" src="https://qiita-image-store.s3.amazonaws.com/0/64665/17cb882a-1b85-e9c1-bf09-b39ca4d167e8.png">

### App Store IDの取得
[App Store Connect](https://appstoreconnect.apple.com)の「マイApp」から該当アプリを選択します。未作成の場合は手順に従い新規Appを作ってください。「App情報」の「一般情報」にある「Apple ID」がApp Store IDになります。
<img width="500" alt="app_store_id.png" src="https://qiita-image-store.s3.amazonaws.com/0/64665/80cdcc6a-f4d6-7369-006b-df30bdc07238.png">

## Firebaseコンソールのアプリ設定の更新
[Firebaseコンソール](https://console.firebase.google.com)の「プロジェクトの設定」内の中に先ほど取得したApp Store IDとチームIDを入力するところがあるのでここを埋めます。この2つの値が設定されていないと、Dynamic Linkで開くアプリとして選択できません。
<img width="500" alt="スクリーンショット 2019-01-02 0.29.02.png" src="https://qiita-image-store.s3.amazonaws.com/0/64665/1496e10b-d8a5-c93e-d6a9-34b116a400ee.png">


# 実装
メールリンク認証は以下の手順で実装することができます。

1. メールリンク認証の有効化
2. Firebase Dynamic Linksの作成
3. Xcode Projectの設定

## メールリンク認証の有効化
[Firebaseコンソール](https://console.firebase.google.com)の「Authentication」セクションを開きます。
<img width="500" alt="console_auth1.png" src="https://qiita-image-store.s3.amazonaws.com/0/64665/47c67077-ee10-f566-c066-11cced9fe85e.png">

上部タブの「ログイン方法」から、「メール/パスワード」と「メールリンク」を有効にし、保存します。
<img width="500" alt="console_auth2.png" src="https://qiita-image-store.s3.amazonaws.com/0/64665/0e863d61-f657-a61e-d102-97c6b9d910bc.png">

## Firebase Dynamic Linksの作成
コンソールの「Dynamic Links」セクションを開きます。Dynamic Linksの利用規約に同意していない場合は同意してください。
<img width="200" alt="dynamic_links1.png" src="https://qiita-image-store.s3.amazonaws.com/0/64665/d21085fb-4bdd-1f45-1f75-52bd4f8750ac.png">

Dynamic Linksに使用するドメインを作成します。すでに作成済みの場合はスキップしてください。ここで作成するドメインは、Xcode上のプロジェクト設定で入力するものになります。サブドメインはアプリ名などにすると良いと思います。
<img width="500" alt="dynamic_links2.png" src="https://qiita-image-store.s3.amazonaws.com/0/64665/0191628c-e22c-341e-9714-cbb1617d9831.png">

ドメインが作成できたら、「新しいダイナミックリンク」をクリックし、表示される手順に沿って作成します。「短縮URLのリンク設定」は、特にこだわりがなければデフォルト値でよいです。
「ダイナミックリンクの設定」では、ディープリンクURLと、今作成しているダイナミックリンクの名前を設定します。
ディープリンクURLは実際に存在するURLでなくても良いので、アプリや処理がわかりやすいものにしておきましょう（e.g. `https://sampleapp.com/signin`）。このURLはアプリ側で、どのような処理を期待するリンクからアプリが開かれたのかを判別するのに用いられます。本記事ではログインを紹介していますが、このURLで振り分けることで、クーポン付与や特定ページの表示などにも利用できます。
ダイナミックリンク名はFirebaseコンソールのみに表示される名前になります。
<img width="500" alt="dynamic_links3.png" src= "https://qiita-image-store.s3.amazonaws.com/0/64665/223a6e86-70d6-5a9f-1d94-63f0b3038ff2.png">

「iOS用のリンク動作の定義」では、「ディープリンクをiOSアプリで開く」を選択し、プルダウンから開くアプリを選択します。他の設定はお好みで構いません。
<img width="500" alt="dynamic_links4.png" src="https://qiita-image-store.s3.amazonaws.com/0/64665/52a10320-cabd-57dd-3f15-21324ca98299.png">

その後に続くAndroidの設定などはどのような値でも大丈夫です。「作成」を押したらダイナミックリンクが追加されます。

## ドメインの承認
先ほどのダイナミックリンクで指定したディープリンクURLのドメインは事前に承認しておく必要があります。メールログインを有効にした「Authentication」セクション内の「ログイン方法」タブの下の方に「承認済みドメイン」という項目があるので、こちらにディープリンクURLのドメインを追加します。（e.g. `https://sampleapp.com/signin`の場合は`sampleapp.com`）

<img width="500" alt="dynamic_li<img width="500" alt="domain.png" src="https://qiita-image-store.s3.amazonaws.com/0/64665/1e434f84-a682-9cd6-86b0-92c0167e553f.png">

以上でFirebaseコンソール側の作業は完了です。

## Xcode Projectの設定
アプリのTargetsの「Capabilities」にて、「Associated Domains」をONにします。「Domains」に`applinks:サブドメイン名.page.link`を追加してください。`applinks:`の後には、Firebaseコンソールの「Dynamic Links」セクションで作成したドメインが入ります。（e.g. `sampleapp.page.link`）
<img width="500" alt="xcode_project.png" src="https://qiita-image-store.s3.amazonaws.com/0/64665/0d215be0-456e-0023-9171-0bbd87d8ffc5.png">

## ユーザのメールアドレスに認証リンクを送信
ここまで完了したら、あとはコーディングだけです。
### `ActionCodeSettings`オブジェクトの作成
`ActionCodeSettings`は、メールリンクの作成方法をFirebaseに伝えるものです。ディープリンクURLには、Firebaseコンソール上で作成したダイナミックリンクのディープリンクURLを指定します。（e.g. `https://sampleapp.com/signin`）

```swift
import FirebaseAuth
// ...
let actionCodeSettings = ActionCodeSettings()
actionCodeSettings.url = URL(string: "ディープリンクURL")
actionCodeSettings.handleCodeInApp = true
actionCodeSettings.setIOSBundleID(Bundle.main.bundleIdentifier!)
```

### メールアドレスに認証リンクを送信
`sendSignInLink`メソッドにメールアドレスと`actionCodeSettings`を渡します。`actionCodeSettings`に指定したディープリンクURLがFirebaseコンソール上で未承認だと、リンクの送信に失敗してしまいます。成功時には「メールのリンクを確認してください」などのアラートを出すと親切ですね。

```swift
Auth.auth().sendSignInLink(toEmail:email,
                           actionCodeSettings: actionCodeSettings) { error in
    if let error = error {
      // リンクの送信失敗
      return
    }
    // リンクの送信成功
}
```

## リンクを確認してログインする
メールに記載されたリンクをタップするとこのようなページに飛びます。OPENをタップするとアプリが開かれます。
<img width="200" alt="web_page.png" src="https://qiita-image-store.s3.amazonaws.com/0/64665/62390726-21da-a952-7e2a-d87672dc8532.png">

このボタンからアプリを開くと、`AppDelegate`内の`application(_:continue:restorationHandler:)`メソッドが呼ばれます。ログインには受信したディープリンクURLと入力されたメールアドレスの2つが必要になるので、`UserDefaults`などを利用してよしなに情報を保持・取得しましょう。[公式ドキュメント](https://firebase.google.com/docs/auth/ios/email-link-auth)では`UserDefaults`に保存し、別画面でボタンを押した際にログイン処理を走らせています。`signIn`メソッドが成功すればメールリンク認証は完了です。

```swift:AppDelegate.swift
import FirebaseAuth
import FirebaseDynamicLinks
// ...
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    guard let url = userActivity.webpageURL else { return true }
    let handled = DynamicLinks.dynamicLinks().handleUniversalLink(url) { [weak self] (link, _) in
            guard let linkURL = link?.url else { return }
            self?.handlePasswordlessSignIn(withURL: linkURL)
        }
        return handled
}

func handlePasswordlessSignIn(withURL url: URL) {
    let link = url.absoluteString
    if Auth.auth().isSignIn(withEmailLink: link) {
        Auth.auth().signIn(withEmail: "メールアドレス", link: link) { [weak self] (result, error) in
            if let error = error {
                // ログイン失敗
                return
            }
            // ログイン成功
        }
    }
}
``` 

# まとめ
Firebaseのメールリンク認証をiOSアプリに実装する方法を解説しました。App Store IDやドメインの取得などは煩雑ですが、パスワードレスなログインによりユーザ体験は非常に向上するので、ぜひ導入してみてください :wink:

# 参考文献
[iOSでメールリンクを使用してFirebase認証を行う](https://firebase.google.com/docs/auth/ios/email-link-auth)