[Xcode/Firebase] Firebase Authenticationの匿名認証

Firebase で認証する一時的な匿名アカウントを、Firebase Authentication で作成して使用できます。一時的な匿名アカウントを使用すると、アプリに登録していないユーザーが、セキュリティ ルールで保護されているデータを使用できるようになります。匿名ユーザーがアプリに登録する場合は、匿名アカウントにログイン認証情報をリンクすると、それ以後のセッションでも引き続き、保護されているデータを使用できます。— 公式より引用

実装

Firebase側の設定

FirebaseコンソールでAuthenticationを選択、ログイン方法 → 新しいプロバイダを追加から匿名認証を許可する。

匿名認証が有効になっていればOK。

ViewModel

import SwiftUI
import Firebase
import FirebaseAuth

final class AuthViewModel: ObservableObject {
    @Published var currentUser: User?
    @Published var email = ""
    @Published var password = ""
    @Published var errorMessage: String?
    @Published var isLoading = false

    private var loginStateHandler: AuthStateDidChangeListenerHandle?

    init() {
        // 認証状態の変化を監視
        loginStateHandler = Auth.auth().addStateDidChangeListener { [weak self] auth, user in
            self?.currentUser = user
            if user != nil {
                self?.errorMessage = nil
            }
        }
    }

    deinit {
        // オブザーバーの解除
        if let handle = loginStateHandler {
            Auth.auth().removeStateDidChangeListener(handle)
        }
    }

    // 匿名サインイン
    func signInAnonymously() {
        isLoading = true
        Auth.auth().signInAnonymously { [weak self] authResult, error in
            defer { self?.isLoading = false }
            if let error = error {
                self?.errorMessage = "匿名認証に失敗しました: \(error.localizedDescription)"
                return
            }

            guard let user = authResult?.user else {
                self?.errorMessage = "匿名ユーザの取得に失敗しました"
                return
            }

            print("匿名認証に成功しました。 UID: \(user.uid), 匿名ユーザー: \(user.isAnonymous)")
            self?.errorMessage = nil

        }
    }

    // サインアウト
    func signOut() {
        do {
            try Auth.auth().signOut()
            errorMessage = nil // エラーメッセージをクリア
        } catch let signOutError as NSError {
            errorMessage = "サインアウトに失敗しました: \(signOutError.localizedDescription)"
        }
    }

    // 匿名アカウントにメール/パスワードをリンク
    func linkEmailPassword() {
        guard let currentUser = Auth.auth().currentUser else {
            errorMessage = "サインインしていません"
            return
        }
        guard currentUser.isAnonymous else {
            errorMessage = "現在ログインしているユーザは匿名ユーザではない"
            return
        }
        isLoading = true
        let credential = EmailAuthProvider.credential(withEmail: email, password: password)

        // 現在の匿名ユーザにクレデンシャルをリンク
        currentUser.link(with: credential) { [weak self] authResult, error in
            defer { self?.isLoading = false }
            if let error = error {
                self?.errorMessage = "リンクに失敗しました: \(error.localizedDescription)"
                return
            }
            print("メール/パスワードアカウントとのリンクに成功しました。")
            self?.errorMessage = nil
        }
    }
}

View

import SwiftUI
import Firebase
import FirebaseAuth

struct ContentView: View {
    @StateObject private var authViewModel = AuthViewModel()

    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                // 認証状態の表示
                if let user = authViewModel.currentUser {
                    LoginInfoView(user: user)

                    if user.isAnonymous {
                        AnonymousUserView()
                    } else {
                        // 匿名でない場合(リンク済みなど)、サインアウトボタンを表示
                        Button {
                            authViewModel.signOut()
                        } label: {
                            Text("サインアウト")
                                .frame(maxWidth: .infinity)
                                .padding()
                                .background(Color.red)
                                .foregroundColor(.white)
                                .cornerRadius(8)
                        }
                    }

                } else {
                    // ログインしていない場合
                    Text("ログインしていません")
                        .font(.headline)
                    Button {
                        authViewModel.signInAnonymously()
                    } label: {
                        Text("匿名でサインイン")
                            .frame(maxWidth: .infinity)
                            .padding()
                            .background(Color.green)
                            .foregroundColor(.white)
                            .cornerRadius(8)
                    }
                }

                // エラーメッセージの表示
                if let errorMessage = authViewModel.errorMessage {
                    Text(errorMessage)
                        .foregroundColor(.red)
                        .padding()
                }

                // ローディングインジケータ
                if authViewModel.isLoading {
                    ProgressView("処理中...")
                }

                Spacer()
            }
            .padding()
            .navigationTitle("匿名認証 -> メールリンク")
        }
    }

    @ViewBuilder
    private func LoginInfoView(user: User) -> some View {
        VStack(alignment: .leading, spacing: 8) {
            Text("ログイン中のユーザー情報")
                .font(.headline)
            Text("UID: \(user.uid)")
            Text("ユーザステータス: \(user.isAnonymous ? "匿名ユーザ" : "認証ユーザ")")
            Text("メール登録: \(user.email ?? "未設定")")
        }
    }

    @ViewBuilder
    private func AnonymousUserView() -> some View {
        // 匿名ユーザーの場合、メール/パスワードリンクUIを表示
        Divider()
        Text("メールアドレスとパスワードでアカウントをリンク")
            .font(.subheadline)
        TextField("メールアドレス", text: $authViewModel.email)
            .autocapitalization(.none)
            .keyboardType(.emailAddress)
            .textFieldStyle(RoundedBorderTextFieldStyle())
        SecureField("パスワード", text: $authViewModel.password)
            .textFieldStyle(RoundedBorderTextFieldStyle())

        Button {
            authViewModel.linkEmailPassword()
        } label: {
            Text("アカウントをリンク")
                .frame(maxWidth: .infinity)
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(8)
        }
        .disabled(authViewModel.isLoading || authViewModel.email.isEmpty || authViewModel.password.isEmpty)
    }
}

AIに導入のメリットデメリットを聞いてみた

AI君
AI君

Firebaseの匿名認証をスマホアプリで利用する際のメリットとデメリットは以下の通りです。

### メリット

1. **簡単な導入**:
   – Firebaseの匿名認証は簡単に実装でき、ユーザーがアカウントを作成する手間を省けます。

2. **ユーザー体験の向上**:
   – 利用者は登録なしでアプリを試すことができるため、敷居が低くなり、初期のエンゲージメントが向上します。

3. **データの保存**:
   – 匿名ユーザーとしてデータを保存し、後でメールアドレスや他の認証方法に切り替えることが可能です。

4. **プライバシーの保護**:
   – ユーザーが個人情報を提供しなくてもアプリを利用できるため、プライバシーを重視するユーザーに適しています。

5. **柔軟なユーザー管理**:
   – 匿名認証を使用することで、ユーザーの行動を追跡しつつ、必要に応じて正式なアカウントに移行できます。

### デメリット

1. **データの持続性がない**:
   – 匿名アカウントはデバイスに依存しているため、アプリをアンインストールしたり、デバイスを変更するとデータが失われる可能性があります。

2. **認証の信頼性が低い**:
   – 匿名ユーザーは特定のユーザーを識別することができないため、悪用されるリスクがあります。

3. **機能の制限**:
   – 一部の機能やサービス(例:購買や特定の設定)には、正式な認証が必要な場合があります。

4. **ユーザーの再エンゲージメントの難しさ**:
   – 匿名ユーザーは将来的にアカウントを作成しない可能性があるため、ユーザーの再エンゲージメントが難しくなることがあります。

5. **分析の難しさ**:
   – 匿名ユーザーの行動を追跡することはできますが、個別のユーザーを特定できないため、分析やマーケティング施策が制限されることがあります。

これらのメリットとデメリットを考慮し、アプリの目的やターゲットユーザーに応じてFirebaseの匿名認証を導入するかどうかを検討することが重要です。