iOS
Swift
Firebase

[Swift] Firebaseを使ってGoogle/Facebookログインを実装してみた

経緯

  • 勉強も兼ねてFirebaseに全任せで、個人のアプリを作成中
  • まずはログイン機能をということで実装してみた

所要時間

ドキュメントが充実しているので、多分2hもかからないくらい簡単

前提条件

以下、完了していること

実装

Googleログイン

簡単なフロー

pod 'Firebase/Auth'
pod 'GoogleSignIn'
  1. Podfileに上記を追加
  2. $ pod install と import
  3. Xcodeのプロジェクトファイル修正(URLスキーム)
  4. delegate処理追加
  5. FirebaseのダッシュボードからGoogleサインインを有効化

ハマりポイント

特になし!

Facebookログイン

簡単なフロー

 pod 'Firebase/Auth
 pod 'FBSDKLoginKit'
  1. FaceBook側でアプリを新規作成(※アプリID/シークレットIDが必要)
  2. Podfileに上記を追加
  3. $ pod install と import
  4. info.plist設定
  5. delegate処理追加(これはFacebookログイン見たほうがいい)
  6. FirebaseのダッシュボードからFaceBookサインインを有効化

ハマりポイント

  • ライブラリが最初上手くインストール出来なかったようで、ビルドエラーの嵐になった
    -> Xcode開き直して、クリーンビルドしたら直った

  • FaceBookのアプリ登録するURLがわかりづらい
    -> Facebookログインから、FaceBookにログインしてあげると登録出来た

コード

import UIKit

class LoginViewController: UIViewController {

    // StoryboardでLoginBaseViewを制約つけて追加
    @IBOutlet weak var loginBaseView: LoginBaseView!

    override func viewDidLoad() {
        super.viewDidLoad()
        self.loginBaseView.delegate = self
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

extension LoginViewController: LoginBaseViewDelegate {
    func loginBaseView(succeededBy type: LoginType) {
        switch type {
        case .firebase:
            print("Success FireBase")
        case .google:
            print("Success Google Login")
        case .facebook:
            print("Success FaceBook Login")
        case .line:
            print("Success Line Login")
        }
        let rootNav = StroyBoards.main.instantiateViewController(withIdentifier: "rootNav")
        self.present(rootNav, animated: true, completion: nil)
    }

    func loginBaseView(failedBy type: LoginType) {
        switch type {
        case .firebase:
            print("Failed FireBase")
        case .google:
            print("Failed Google Login")
        case .facebook:
            print("Failed FaceBook Login")
        case .line:
            print("Failed Line Login")
        }
    }
}
import UIKit
import Firebase
import GoogleSignIn
import FBSDKLoginKit

protocol LoginBaseViewDelegate: class {
    func loginBaseView(succeededBy type: LoginType)
    func loginBaseView(failedBy type: LoginType)
}

enum LoginType {
    case firebase
    case google
    case facebook
    case line
}

class LoginBaseView: UIView, GIDSignInDelegate, GIDSignInUIDelegate, FBSDKLoginButtonDelegate {

    weak var delegate: LoginBaseViewDelegate?
    lazy var gidSignInButton: GIDSignInButton = createGoogleButton()
    lazy var fbLoginButton: FBSDKLoginButton = createFaceBookButton()

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.addLoginButtons()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.addLoginButtons()
    }

    // MARK: - GIDSignInDelegate

    @available(iOS 9.0, *)
    func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
        return
            GIDSignIn.sharedInstance().handle(
                url,
                sourceApplication:options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String,
                annotation: [:]
        )
    }

    func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error?) {
        // ログインエラーの場合(キャンセルも含む)
        if let _ = error {
            self.delegate?.loginBaseView(failedBy: .google)
            return
        }
        guard let authentication = user.authentication else {
            return
        }

        // TODO: トークンはUserDefaultなりに保存して、次回以降のログインを省略
        // TODO: 何れかでログインできている場合は、ログイン画面を表示しなくて良さげ
        let credential =
            GoogleAuthProvider.credential(
                withIDToken: authentication.idToken,
                accessToken: authentication.accessToken
        )

        linkToFireBase(credential: credential)
    }

    func sign(_ signIn: GIDSignIn!, present viewController: UIViewController!) {
        // この辺の使い道がまだわかってない(※ただこれがないとエラーになる)
        print("present")
    }

    func sign(_ signIn: GIDSignIn!, dismiss viewController: UIViewController!) {
        // この辺の使い道がまだわかってない(※ただこれがないとエラーになる)
        print("dismiss")
    }

    // MARK: - FBSDKLoginButtonDelegate

    func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) {
        if let _ = error {
            self.delegate?.loginBaseView(failedBy: .facebook)
            return
        }

        let credential = FacebookAuthProvider.credential(withAccessToken: FBSDKAccessToken.current().tokenString)
        linkToFireBase(credential: credential)
    }

    // TODO: ログアウト処理をどうするか
    func loginButtonDidLogOut(_ loginButton: FBSDKLoginButton!) {
        print("")
    }

    // MARK: - Private Method

    private func createGoogleButton() -> GIDSignInButton {
        GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID
        GIDSignIn.sharedInstance().delegate = self
        GIDSignIn.sharedInstance().uiDelegate = self
        // heightとかも可変にせねば...
        let button = GIDSignInButton(frame: CGRect(x: 0, y: 0, width: self.frame.size.width, height: 50))
        return button
    }

    private func createFaceBookButton() -> FBSDKLoginButton {
        // heightとかも可変にせねば...
        let frame = CGRect(x: 0, y: self.gidSignInButton.frame.maxY, width: self.frame.size.width, height: 50)
        let button = FBSDKLoginButton(frame: frame)
        button.delegate = self
        return button
    }

    private func addLoginButtons() {
        // lazyなので、参照するタイミングでcreatexxxButton()が呼ばれる
        self.addSubview(gidSignInButton)
        self.addSubview(fbLoginButton)
    }

    private func linkToFireBase(credential: AuthCredential) {
        Auth.auth().signInAndRetrieveData(with: credential) { (authResult, error) in
            if let _ = error {
                self.delegate?.loginBaseView(failedBy: .firebase)
            }
            // ログイン成功
            self.delegate?.loginBaseView(succeededBy: .firebase)
        }
    }
}

画面

iphone8のシミュレータ↓
スクリーンショット 2018-10-11 0.58.06.png

課題

  • 各認証先(Google/Facebook)の表示を日本語に変える
  • ログイン失敗時のハンドリング(違うアプリで認証してねを促す方法)
  • ログインした後の認証情報の扱い方(1度ログインしたら、次回起動時はログインさせないみたいな)調整
  • ログアウト処理はログイン画面のアイテムからイベントがキックされないので、その調整
  • FacebookとGoogleのログインで別のアカウントが作成されてしまう問題
  • 各ボタンのデザイン調整...etc

参考

最後に

ほぼコピペでいけるくらい簡単です。
まだまだリファクタリングも必要で、課題もたくさん残っているので、そいつら直しつつLineとTwitterのログインも実装したいなぁと思ってます。
とりあえずログイン機能の実装が終わらないとスタートしないので、早めに終わらせたい!