【iOS】Firebaseど素人のFirebase入門 認証編

Firebaseど素人だったので、Firebase学習の為に公式ドキュメントを見て、学びをサンプルアプリという形でアウトプットしました。
まずは`Authentication`（認証）に挑戦！

##作ったサンプルアプリのデモ
![イメージ.GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678954/6e930cdb-5e7a-a306-c5a1-edc575599287.gif)

現在使用中のユーザーメールアドレスがラベルに表示されるアプリです。

・メールアドレスとパスワードを使った新規登録（サインアップ）
・登録済みのアカウントの認証（サインイン）
・サインアウト
・匿名認証

この機能の実装を行いました。


##まずはFirebaseに登録
Firebase公式ドキュメントに沿って、Firebase登録とFirebase SDKをアプリに追加する。
 <a href="https://firebase.google.com/docs/ios/setup?hl=ja">Firebase を iOS プロジェクトに追加する</a>

##アプリ内でFirebaseの初期化を行う
AppDelegate.swiftにFirebaseをインポート

```swift:AppDelegate.swift
import Firebase
```

`didFinishLaunchingWithOptions`内で`FirebaseApp.configure()`を宣言

```swift:AppDelegate.swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        FirebaseApp.configure()
        return true
    }
```

##使用するログインプロバイダのステータスを変更する
今回はメール/パスワード認証と匿名認証をログインに使用するので、Firebaseに登録したアプリのプロジェクト内`Authentication`の`Sign-in method`で
<b>メール/パスワード</b>
<b>匿名</b>
こちらの二つのステータスを有効に変更します。
![スクリーンショット 2021-01-15 15.43.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678954/352ac017-faee-cbe4-bd1a-5da3114f9e1f.png)

##ViewControllerにFirebaseをインポート
作成を進めるファイルにもFirebaseをインポートする。

```swift:AuthViewController.swift
import Firebase
```

##メール/パスワード認証
###新規登録

新規登録には`createUser(withEmail:, password:, completion:)`を使用します。

```swift
Auth.auth().createUser(withEmail: email, password: password) { (authResult, error) in
     guard let user = authResult?.user, error == nil else {
          print("登録に失敗しました:" ,error!.localizedDescription)
          return
     }
     print("登録に成功しました", user.email!)
 }
```

###サインイン
サインインには、`signIn(withEmail:, password:, completion:)`を使用します。

```swift
 Auth.auth().signIn(withEmail: email, password: password) { (authResult, error) in
     guard let user = authResult?.user, error == nil else {
          print("サインインに失敗しました:" ,error!.localizedDescription)
          return
      }
      print("サインインに成功しました", user.email!)
    } 
```

##匿名ログイン
匿名ログインには`Auth.auth().signInAnonymously(completion:)`を使います。

```swift
Auth.auth().signInAnonymously { (authResult, error) in
    guard let user = authResult?.user, error == nil else {
         print("匿名サインインに失敗しました:" ,error!.localizedDescription)
         return
     }
     print("匿名サインインに成功しました", user.uid)
  }
```


##サインアウト
`signOut()`を呼ぶだけです。

```swift
 do {
     try Auth.auth().signOut()
     } catch let signOutError as NSError {
         print("サインアウトに失敗しました:", signOutError)
     }
```

##現在ログインしているユーザーを取得する

>現在ログインしているユーザーを取得するには、Auth オブジェクトでリスナーを設定することをおすすめします。

とのことで、リスナーを設定してみました。

###1.`AuthStateDidChangeListenerHandle`をグローバル変数として定義

```swift:AuthViewController.swift
    var authHandle: AuthStateDidChangeListenerHandle?
```

###2.`viewWillAppear`で`addStateDidChangeListener(listener:)`をアタッチ

```swift:
override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        authHandle = Auth.auth().addStateDidChangeListener({ (auth, user) in
        //ログイン状態が変更された時の処理を書く
        })
    }
```
`viewWillAppear`でアタッチしたこのリスナーは、ユーザーのログイン状態が変更される度に呼び出されます。
変更される度に実行したい処理をここに書きます。
とても便利ですね。
ログインされてるなら別ページに遷移、ログイン無しなら登録ページを表示などの使い分けにも有効そうです。

その他の方法では、`currentUser`でログインユーザーの状態も取得出来るようです。
>currentUser を使用することでも、現在ログインしているユーザーを取得できます。ユーザーがログインしていない場合、currentUser は nil です。

###3.`viewWillDisappear`でリスナーを切り離します。
```swift
override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        Auth.auth().removeStateDidChangeListener(authHandle!)
    }
```

##まとめ
Firebaseの公式ドキュメントはとても充実しており、日本語ページもあるので簡単にここまでは進めれました。
もっとしっかり公式ドキュメントを読み、理解を深めていきたいと思います。
何か間違いがありましたら、優しく指摘していただけると幸いです🙇‍♂️

##参考
<a href="https://firebase.google.com/docs/auth/ios/start?hl=ja">iOS で Firebase Authentication を使ってみる</a>

##サンプリアプリのコード全体
```swift:
import UIKit
import Firebase

class AuthViewController: UIViewController {
    
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!
    @IBOutlet weak var currentUserEmailLabel: UILabel!
    
    @IBOutlet weak var signUpButton: UIButton!
    @IBOutlet weak var signInButton: UIButton!
    
    var authHandle: AuthStateDidChangeListenerHandle?
    
    override func viewDidLoad() {
        super.viewDidLoad()

        signUpButton.layer.cornerRadius = 22
        signInButton.layer.cornerRadius = 22
        emailTextField.delegate = self
        passwordTextField.delegate = self
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        authHandle = Auth.auth().addStateDidChangeListener({ (auth, user) in
            if let currentUser = user {
                //もし、ユーザーが匿名で利用していたら
                if currentUser.isAnonymous {
                    self.currentUserEmailLabel.text = "匿名で利用中"
                } else {
                    self.currentUserEmailLabel.text = currentUser.email
                }
            } else {
                //ログインをしていない場合
                self.currentUserEmailLabel.text = "現在ログイン中のユーザーはいません"
            }
        })
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        Auth.auth().removeStateDidChangeListener(authHandle!)
    }
    
    
    // MARK: - Button Actions
    
    //サインアウト
    @IBAction func didTapSignOut(_ sender: UIBarButtonItem) {
        do {
            try Auth.auth().signOut()
        } catch let signOutError as NSError {
            print("サインアウトに失敗しました:", signOutError)
        }
    }
    
    //サインアップ
    @IBAction func didTapSignUp(_ sender: UIButton) {
        
        if let email = emailTextField.text, let password = passwordTextField.text {
            Auth.auth().createUser(withEmail: email, password: password) { (authResult, error) in
                guard let user = authResult?.user, error == nil else {
                    print("登録に失敗しました:" ,error!.localizedDescription)
                    return
                }
                print("登録に成功しました", user.email!)
            }
        }
    }
    
    //サインイン
    @IBAction func didTapSignIn(_ sender: UIButton) {
        
        if let email = emailTextField.text, let password = passwordTextField.text {
            Auth.auth().signIn(withEmail: email, password: password) { (authResult, error) in
                guard let user = authResult?.user, error == nil else {
                    print("サインインに失敗しました:" ,error!.localizedDescription)
                    return
                }
                print("サインインに成功しました", user.email!)
            }
        }
    }
    
    //匿名サインイン
    @IBAction func didTapSignInAnonymously(_ sender: UIButton) {
        Auth.auth().signInAnonymously { (authResult, error) in
            guard let user = authResult?.user, error == nil else {
                print("匿名サインインに失敗しました:" ,error!.localizedDescription)
                return
            }
            print("匿名サインインに成功しました", user.uid)
        }
    }
}

// MARK: - TextField Delegate Methods

extension LoginViewController: UITextFieldDelegate {
    
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
}
```