22
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[iOS] Firebaseメールリンク認証を実装してみた

Last updated at Posted at 2021-03-03

#はじめに
Firebase Authenticationを使用すれば、アプリでの様々なユーザー認証 (例えば、パスワード、電話番号、Googleアカウントなど)を比較的容易に実装することができます。
パッと思いつく認証方法はメール/パスワード認証かなと思います。
しかし、このセキュリティはユーザー視点に立つとパスワードを覚えるのが面倒であるといった、アプリを使用する障壁となり得ます。
本記事では、パスワード不要のメールリンク認証 (iOS)についての実装手順をまとめてみました。

#環境
[Firebase SDK (iOS)] Version 7.7.0
[Xcode] Version 12.4
[Swift] Version 5.3.2
[iOS] 14.4
[MacOS] 10.15.7

#メールリンク認証とは
メールリンク認証とは、ログイン用のリンクを含むメールをユーザーに送信し、そのリンクからログインしてもらう認証方法です。
1.PNG
メールリンク認証にはいくつもメリットがあります。

  • 登録とログインが簡単
  • ユーザーがメールアドレスの正当な所有者であることを確認できる
  • パスワード不要 (覚える必要なし)で、安全にログインできる
    などです。

#本記事で作成するサンプル
今回は、ユーザーのメールアドレスを受け取り、送信されたメールのリンクからアプリを起動してログインするというシンプルなアプリを想定します。
2.PNG

#実装手順
公式ドキュメントに従い、以下の流れで実装していきます。

  1. FirebaseをiOSプロジェクトに追加 (本記事では省略します)
  2. Firebaseプロジェクトでメールリンクログインを有効にする
  3. Dynamic Linksを構成する
  4. ユーザーのメールアドレスに認証リンクを送信して、アプリでユーザーのログインを完了する

###1. FirebaseをiOSプロジェクトに追加 (省略)
本記事では省略します。(公式ドキュメント 「FirebaseをiOSプロジェクトに追加する」を参照してください)
今回は、ドキュメントでも推奨されていますがCocoaPodsでFirebaseをインポートします。Podfileファイルには以下のPodを含めます。

Podfile
  pod 'Firebase/Analytics'
  pod 'Firebase/Auth'
  pod 'Firebase/DynamicLinks'

(21/09/04 追記)
Xcode12.5以降で、正式にSwift PackageManagerでFirebaseをインストールできるようになりました。
Swift PackageManagerを使用してFirebaseをインストールする

###2. Firebaseプロジェクトでメールリンクログインを有効にする
Firebase コンソールで[Authentication]セクションを開き、[始める]を選択します。
3.png  4.png
[Sign-in method]タブで[メール/パスワード]を編集します。
5.png
[メール/パスワード]と[メールリンク]の両方を有効にして保存します。
6.png
これでメールリンクが有効となりました。

###3. Firebase Dynamic Linksを構成する

####3-1. Firebase Dynamic Linksとは

Firebase Dynamic Linksとは、アプリのインストールの有無に関わらす、複数のプラットフォームで機能するディープリンクです。
Dynamic Links により、ユーザーがリンクを開いたプラットフォームで得られる最高のエクスペリエンスを提供できるようになります。
iOS または Android でダイナミックリンクを開くと、ネイティブアプリのリンク先のコンテンツに直接移動します。デスクトップ ブラウザでダイナミック リンクを開くと、ウェブサイト上の同じコンテンツに移動します。
さらに、ダイナミック リンクはアプリのインストールが必要な場面でも利用できます。
たとえば、ダイナミック リンクを開いたユーザーの iOS デバイスまたは Android デバイスにアプリがインストールされていない場合にインストールを促し、インストール完了後にアプリを起動してリンクを開くといったこともできます。

Firebase Dynamic Linksより引用

####3-2. App Store IDとチームIDを取得する
ダイナミックリンクを作成する際に、「App Store ID」と「チームID」が必要となるので先に取得しておきます。
すでに取得済みの方はスキップしてください。

#####チームIDの取得
Apple Developerアカウントの[Certifications, IDs & Profiles]セクションの[Identifiers]セクションで確認できます。**「App ID Prefix」**がチームIDです。
アプリを未登録の方は、以下の手順で登録できます。
[Certifications, IDs & Profiles]セクションをクリックします。
7.png
[Identifiers]セクションのボタンをクリックします。
8.png
App IDs」を選択して「Continue」で次に進みます。
9.png
Select a type」で「App」を選択して「Continue」で次に進みます。
10.png
Description」にはアプリの簡単な説明を入力します。アプリ名でも良さそうです。
Bundle ID」にはXcodeプロジェクトのBundle Identifierを正確に入力してください。
Capabilities」では、アプリリリース時にはアプリの機能を漏れなく選択する必要がありますが、今回は最低限必要な「Associated Domains」を選択して次に進みます。
11.png
App ID Prefix」がチームIDです。これでチームIDの確認を完了しました。
12.png

#####App Store IDの取得
App Store Connectの「マイApp」から自分の登録しているアプリをクリックして、[App情報]セクションにある**「Apple IDがApp Store IDです。
アプリを未登録の方は以下の手順で登録できます。
まず「マイApp」をクリックします。
13.png
作成しているアプリを「新規App」から追加します。
14.png
アプリの名前」、「使用する主な言語」、「Bundle ID」、「SKU」を設定します。
SKUはアプリを区別できる名前であれば何でも良いと思います。
今回はアプリ名にしました。
15.png
作成を終えたら、[App情報]セクションをクリックして
Apple ID」**を確認します。これがApp Store IDに当たるので、確認完了です。
16.png

####3-3. Dynamic Linksを作成する
公式ドキュメントによると、Dynamic Linksの作成方法は4つありますが、今回はFirebase コンソールで作成します。
[Dynamic Links]セクションを開き、「始める」をクリックします。
17.png  18.png
Dynamic LinksのURLドメインを作成します。
ドメインは特に指定しなければGoogle提供の.page.linkが与えられるので、それを使用します。
サブドメインはアプリ名などの分かりやすいものが良いです。(例えば、今回ならURLをopenapp.page.linkにするとか)
19.png20.png
次に、「新しいダイナミック リンク」ボタンからダイナミックリンクを作成します。
21.png
短縮URLのリンク設定では、デフォルト値でも問題ありません。今回は分かりやすいように「open」としています。
22.png
次に、ダイナミックリンクの設定です。
ディープリンクURLによって特定のコンテンツに直接遷移することが可能となります。ディープリンクは、オリジナルのURLで良いようなので遷移先が分かるように設定します。
今回は、アプリ自身に移動するので、[Authentication]セクションの承認済みドメインURLをコピーして使用しています。
ダイナミックリンク名」は処理が分かりやすいように、「Open App」のようにします。
23.png
24.png
次に、iOSデバイスでリンクを開いたときの動作を設定します。
今回は「ディープリンクでiOSアプリを開く」を選択して、アプリを指定します。
このとき、Firebase コンソールの[プロジェクト設定]セクションにおいて、App Store IDチームIDを設定している必要があります。
設定していない場合、アプリを指定しようとすると、「App Store IDとチームIDをここに追加してください」という警告が表示されるので、未設定の方は入力してください。
25.png
26.png
次に、アプリがインストールされていないときの動作を設定します。
今回はApp Storeに移動します。
28.png
次にAndroidでの動作設定や、その他詳細オプションを設定できますが、今回は特に何も設定をせずに進めています。
29.png  30.png
以上で、ダイナミックリンクの作成は完了です。
リンクURLをコピーして、[Authentication]セクションの[Sign-in method]タブ内にある「承認済みドメイン」に、ダイナミックリンクURLのドメインを追加します。
31.png
32.png
下記のように、追加したURLドメインが一覧に表示されていればOKです。
33.png
以上で、コンソールにおけるDyanmic Linksの構成は完了です。
次にXcodeプロジェクトの設定を行います。

####3-4. Xcodeプロジェクトの設定
TARGETS」の[Signing & Capabilities]タブにある「+Capability」をクリックして「Associated Domains」を追加します。
34.png
Domains」に「applinks:example.page.link」を入力してください。「example.page.link」はFirebaseコンソールで作成したダイナミックリンクURLのドメインです。
35.png

以上でXcodeプロジェクトでの設定も完了です。

###4. メールアドレスに認証リンクを送信して、アプリでユーザーのログインを完了する
####4-1. ActionCodeSettingsオブジェクトを作成する
メールリンクの作成方法をFirebaseに伝えるActionCodeSettingsオブジェクトを作成します。

SendEmailViewController

    //・・・
    import Firebase

    @IBOutlet private weak var emailTextField: UITextField! //ユーザーからメールアドレスを受け取るUIを準備しておきます

    //・・・

    let email = emailTextField.text ?? "" //ユーザーのメールアドレス

    let actionCodeSettings = ActionCodeSettings() //メールリンクの作成方法をFirebaseに伝えるオブジェクト
    actionCodeSettings.handleCodeInApp = true //ログインをアプリ内で完結させる必要があります
    actionCodeSettings.setIOSBundleID(Bundle.main.bundleIdentifier!) //iOSデバイス内でログインリンクを開くアプリのBundle ID
    //リンクURL
    var components = URLComponents()
    components.scheme = "https"
    components.host = "maillinkdemo.page.link" //Firebaseコンソールで作成したダイナミックリンクURLドメイン
    let queryItemEmailName = "email" //URLにemail情報(パラメータ)を追加する
    let emailTypeQueryItem = URLQueryItem(name: queryItemEmailName, value: email)
    components.queryItems = [emailTypeQueryItem]
    guard let linkParameter = components.url else { return }
    actionCodeSettings.url = linkParameter  
    //actionCodeSettingsの作成完了

    //・・・

####4-2. メールアドレスに認証リンクを送信する
ユーザーのメールアドレスemailと作成したactionCodeSettingsを使用して、ユーザーのメールアドレスに認証リンクを送信します。
ログインメールの送信時に、メールアドレスをローカルに保存しておきます。
Firebase Authでは、認証フロー完了時にユーザーのメールアドレスとログインリンクの送信アドレスが一致するかどうかを確認するため、ローカルに保存しておくことで、ユーザーに再度メールアドレスを入力してもらわなくても認証フローを完了できます。

メールリンクログインを完了する | iOS でメールリンクを使用して Firebase 認証を行う

SendEmailViewController
    //・・・

    //ユーザーのメールアドレスに認証リンクを送信
    Auth.auth().sendSignInLink(toEmail: email, actionCodeSettings: actionCodeSettings) { (err) in
        if let err = err {
            print("送信失敗")
            return
        }
        print("送信完了")

        //後で認証に使用するのでローカルにメールアドレスを保存しておく
        UserDefaults.standard.set(email, forKey: "email")

        //・・・
        //アラートを表示するなど、ユーザーにメールの確認を促す処理
           
    }

####4-3. リンクを確認して、ログインを完了する
メールを受信したら、それがメールリンク認証用であることを確認してログインを完了します。
ユーザーがメールに含まれたリンクをタップすると、次のようなページに遷移します。
37.PNG
OPEN」ボタンをタップするとアプリが開きます。 (アプリがインストールされていない場合は、リリースしていればAppStoreを開きます)
アプリを開くと、SceneDelegateクラスのscene(_:continue:)メソッドが呼ばれるので、この中にログイン完了の処理を書きます。今回は、サインインしたら画面を切り替えるというシンプルな処理にしています。
SceneDelegateはiOS13から導入されたようで、公式ドキュメントではAppDelegateクラスでの処理を紹介しているので、SceneDelegateクラスが不要で削除されている方はそちらを参考にしてください。
SceneDelegateクラスでの実装は、下記の記事が分かりやすく参考にさせていただきました。
参考: [iOS13] SceneDelegateでDynamicLinksからのURLをハンドリングする

SceneDelegate
import Firebase

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    //・・・
    
    var window: UIWindow?

    //ユーザーのアクティビティオブジェクトの情報を受信したときに呼ばれる
    func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {

        //userActivityプロパティからリンクURLを取得
        guard let url = userActivity.webpageURL else { return }
        let link = url.absoluteString

        if Auth.auth().isSignIn(withEmailLink: link) {
            //ローカルに保存していたメールアドレスを取得
            guard let email = UserDefaults.standard.value(forKey: "email") as? String else {
                print("メールアドレスが存在しません")
                return
            }
            //ログイン処理
            Auth.auth().signIn(withEmail: email, link: link) { (auth, err) in
                if let err = err {
                    print("ログイン失敗")
                    return
                }
                print("ログイン成功")
                
                //ログイン成功時の処理 (例えば、今回は画面切り替えなどの処理)
                guard let scene = (scene as? UIWindowScene) else { return }
                let window = UIWindow(windowScene: scene)
                let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
                let didSignInVC = storyboard.instantiateViewController(withIdentifier: "DidSignInViewController")
                window?.rootViewController = didSignInVC
                self.window = window
                window.makeKeyAndVisible()
            }
        }
    }

    //・・・
}

####4-4. [補足] サインアウトする
サインアウト処理は以下のように簡単に書けます。

DidSignViewController

import UIKit
import Firebase

class DidSignInViewController: UIViewController {
    
    //・・・

    //サインアウトボタンを準備しておく
    @IBAction func signButtonTapped(_ sender: UIButton) {
        //ログイン状態ならば、サインアウト処理を行う
        if let user = Auth.auth().currentUser {            
            do {
                try Auth.auth().signOut()
                print("サインアウト完了")
                //画面切り替えなど、サインアウト後の処理
                //・・・
                
            } catch let signOutError as NSError {
                print("失敗")
                return
            }
        }
    }
}

signOut()メソッドは完了のタイミングが分からないので、少し注意が必要そうです。

#少しハマったこと
これまでに何回かDynamicLinksを導入してみましたが、毎回実装した直後にはメールのリンクから**「Open link in app?」**のページに上手く遷移されない現象が起きました。
これは断言できるわけではないのですが、DynamicLinksを設定してからFirebaseのシステムに反映されるまでに時間がかかっているのかもしれません。
これといった対策をしないまま、次の日あたりに上手く機能することがあり、確実な原因が分からないままになっています。
同じようなことが起きた方、理由がわかる詳しい方がいればぜひコメントで共有していただけると幸いです!(笑)

#最後に
Firebaseを利用したメールリンク認証の実装手順をまとめてみました。
主観ですが、パスワードを用いたログインに比べてユーザー視点でのメリットが多いと感じました。
今回実装してみて、ダイナミックリンクはFirebaseコンソールで簡単に作成できるものの、細かい設定などに少し手こずりました。
**「参考文献」**に記載しましたが、iOSでのFirebase Dynamic Linksの実装について分かりやすい記事をいくつも参考にさせていただきました。
しかし、iOSアップデートなどにより、そのまま手順通りにやっても上手くいかない箇所もあるため、合わせてこの記事がほんの少しでも役に立てば幸いです。
初めて記事を投稿したため、分かりづらい点が多いと思いますが、今後もアップデートしていければと思います。

#参考文献
この記事は以下の情報を参考にしました。

22
15
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
15

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?