📂

【Swift】新規登録かログインか判定する(Firebase)

2023/12/13に公開

初めに

今回は Swift × FirebaseAuth × Google Sign In で実装した Google のサインインに関して、新規登録かログインのユーザーかを判定する必要があったので、その方法を共有したいと思います。

記事の対象者

  • SwiftUI 学習者
  • FirebaseAuth を用いて Google Sign In を実装したい方
  • Google Sign In において新規登録とログインを明確に区別したい方

目的

改善前

Google Sign Inを利用したユーザーは、新規登録のユーザーなのか、すでに一度登録していて、ログインしているユーザーなのか判別できない
したがって、Google でサインインした後の遷移先を分けられない

新規登録の場合 ログインの場合

通常の createUser や signIn であれば遷移先は容易に指定可能でしたが、Google Sign In の場合は設定できていませんでした。

改善後

新規登録の場合はチュートリアル画面に、ログインの場合はホーム画面に遷移といったように遷移先を振り分けることができる

新規登録の場合 ログインの場合

実装

今回は Firebase プロジェクトとの連携や Google Sign In パッケージの導入は済んでいる状態を想定して実装を進めていきます。
詳しい実装に関しては Firebase の公式ページをご参照ください

改善前

AuthViewModel
func signInWithGoogle() async -> Bool {
    guard let clientID = FirebaseApp.app()?.options.clientID else {
        fatalError("No client ID found in Firebase configuration")
    }
    let config = GIDConfiguration(clientID: clientID)
    GIDSignIn.sharedInstance.configuration = config
    guard let windowScene = await UIApplication.shared.connectedScenes.first as? UIWindowScene,
    let window = await windowScene.windows.first,
    let rootViewController = await window.rootViewController else {
        return false
    }
        
    do {
        let userAuthentication = try await GIDSignIn.sharedInstance.signIn(withPresenting: rootViewController)
            
        let user = userAuthentication.user
	guard let idToken = user.idToken else {
            print("error occured during google sign in")
            return false
        }
        let accessToken = user.accessToken
        let credential = GoogleAuthProvider.credential(
            withIDToken: idToken.tokenString,
            accessToken: accessToken.tokenString
        )
            
        let result = try await Auth.auth().signIn(with: credential)
        return true
    }
    catch {
        print(error.localizedDescription)
        return false
    }
}

改善後

AuthViewModel
func signInWithGoogle() async -> Bool {
    guard let clientID = FirebaseApp.app()?.options.clientID else {
        fatalError("No client ID found in Firebase configuration")
    }
    let config = GIDConfiguration(clientID: clientID)
    GIDSignIn.sharedInstance.configuration = config
    guard let windowScene = await UIApplication.shared.connectedScenes.first as? UIWindowScene,
    let window = await windowScene.windows.first,
    let rootViewController = await window.rootViewController else {
        return false
    }
        
    do {
        let userAuthentication = try await GIDSignIn.sharedInstance.signIn(withPresenting: rootViewController)
            
        let user = userAuthentication.user
	guard let idToken = user.idToken else {
            print("error occured during google sign in")
            return false
        }
        let accessToken = user.accessToken
        let credential = GoogleAuthProvider.credential(
            withIDToken: idToken.tokenString,
            accessToken: accessToken.tokenString
        )
            
        let result = try await Auth.auth().signIn(with: credential)            
+       if let isNewUser = result.additionalUserInfo?.isNewUser, isNewUser {
+           print("This is a new user.")
+           self.isRegisterSuccess = true
+       } else {
+           print("This is an existing user")
+           self.isLoginSuccess = true
+       }
        return true
    }
    catch {
        print(error.localizedDescription)
        return false
    }
}

AuthResult 型の result として、Auth.auth().signIn(with: credential) の返り値を指定しています。
そして、AuthResultadditionalUserInfo を持っています。
additionalUserInfo の中には、今回使用したい isNewUser が含まれています。

この isNewUser は公式ドキュメントの説明の「Returns whether the user is new or existing」にある通り、Google を使ってサインインしたユーザーが新しいユーザーなのか、既に一度登録したことがあるユーザーなのかを Boolean 形式で返します。

また、コードにあった isRegisterSuccess isLoginSuccess に関して、新規登録またはログインが成功した際に true にすることで、View側にそれを伝播することができます。

この二つの変数に関しては、外部から参照できるように @Published として指定しておきます。

AuthViewModel
@Published var isRegisterSuccess: Bool = false
@Published var isLoginSuccess: Bool = false

View側で使用する

まずは以下のように ObservedObject として AuthViewModel を受け取ります。

EntryAuthView
@ObservedObject var authViewModel: AuthViewModel = AuthViewModel()

次にボタンのアクションで signInWithGoogle を指定します。
なお、非同期処理で行うため、Task内に処理を記述します。

EntryAuthView
Button(action: {
    Task {
        do {
            await authViewModel.signInWithGoogle()
        }
    }
}) {
    Text("Googleでサインイン")
}

最後に AuthViewModel で Published として指定していた isRegisterSuccessisLoginSuccess をそれぞれチュートリアル画面、ホーム画面への遷移のトリガーとして指定します。

EntryAuthView
.fullScreenCover(isPresented: $authViewModel.isRegisterSuccess) {
    IntroView()
}
.fullScreenCover(isPresented: $authViewModel.isLoginSuccess) {
    HomeView()
}

これで、新規登録のユーザーとログインのユーザーで表示させる画面を切り替えることができるようになったかと思います。

まとめ

最後まで読んでいただいてありがとうございました。
Google Sign In に関しては新規登録とログインの切り分けができておらず、すぐに変更しました。
今回紹介した isNewUser 以外にも getProfilegetUsername などもあったので、機会があれば触れてみたいと思います。
誤っている点等あればご指摘いただければ幸いです。

参考

https://firebase.google.com/docs/auth/ios/google-signin?hl=ja

https://firebase.google.com/docs/reference/kotlin/com/google/firebase/auth/AdditionalUserInfo

Discussion