Firebase Auth (Swift/iOS)

認証ライブラリFirebaseSwiftiOSOAuthJWTモバイルセキュリティ

認証ライブラリ

Firebase Auth (Swift/iOS)

概要

Firebase Auth for iOS(Swift)は、Googleが提供するFirebaseプラットフォームのiOS向け認証SDKです。2025年現在、Swift Package Managerによる完全な統合をサポートし、iOS、macOS、tvOS、watchOSアプリケーション向けに統一された認証体験を提供しています。メール・パスワード認証、OAuth(Google、Apple、Facebook等)、電話番号認証、匿名認証など豊富な認証方式をサポートし、SwiftUIとUIKitの両方で使用できます。Appleの最新セキュリティガイドラインに準拠し、企業レベルのユーザー管理機能をFirebase Consoleと連携して提供します。

詳細

Firebase Auth for iOS(Swift)は、Appleエコシステムに最適化された認証ソリューションです。主な特徴:

  • Apple プラットフォーム最適化: iOS、macOS、tvOS、watchOSでの統一API
  • Swift Package Manager対応: 2025年推奨のモダンな依存管理
  • SwiftUI/UIKit統合: 両フレームワークでのネイティブサポート
  • 豊富な認証方式: メール・パスワード、OAuth、電話番号、Face ID、Touch ID、カスタムトークン認証
  • リアルタイム状態管理: AuthStateDidChangeListenerによる自動状態監視
  • セキュリティ機能: 多要素認証、メール認証、HTTPS通信、カスタムクレーム
  • Firebase統合: Firestore、Cloud Functions、Analytics等との完全統合

メリット・デメリット

メリット

  • Googleが提供する企業グレードの認証基盤で信頼性が高い
  • Apple公式ガイドラインに準拠したセキュリティ実装
  • Swift Package Managerによるモダンで簡単な依存管理
  • SwiftUIとUIKitの両方でネイティブサポート
  • Firebase Consoleによる包括的なユーザー管理とアナリティクス
  • Apple Sign In、Face ID、Touch IDとの完全統合

デメリット

  • Firebaseエコシステムに依存するベンダーロックイン
  • カスタム認証ロジックの実装に制約がある
  • Firebaseの利用料金が発生(無料枠あり)
  • Apple以外のプラットフォームでは使用できない
  • インターネット接続が必要な機能がある

参考ページ

書き方の例

Swift Package Manager によるセットアップとインストール

// Package.swift への依存関係追加
// または Xcode の File > Add Packages から追加
// Firebase iOS SDK: https://github.com/firebase/firebase-ios-sdk.git

// AppDelegate.swift - Firebase初期化
import UIKit
import Firebase

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    func application(_ application: UIApplication, 
                    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Firebase設定ファイル(GoogleService-Info.plist)を追加後に初期化
        FirebaseApp.configure()
        return true
    }
}

// SwiftUI アプリの場合
// App.swift
import SwiftUI
import Firebase

@main
struct MyApp: App {
    init() {
        FirebaseApp.configure()
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

// iOS シミュレーター用のエミュレーター設定(開発環境)
#if DEBUG
Auth.auth().useEmulator(withHost: "localhost", port: 9099)
#endif

基本的なメール・パスワード認証の実装

// AuthenticationViewModel.swift - 認証ロジック
import Foundation
import Firebase
import Combine

class AuthenticationViewModel: ObservableObject {
    @Published var user: User?
    @Published var isLoading = false
    @Published var errorMessage = ""
    
    private var authStateHandle: AuthStateDidChangeListenerHandle?
    
    init() {
        addAuthStateListener()
    }
    
    deinit {
        removeAuthStateListener()
    }
    
    // 認証状態リスナーの設定
    func addAuthStateListener() {
        authStateHandle = Auth.auth().addStateDidChangeListener { [weak self] _, user in
            DispatchQueue.main.async {
                self?.user = user
            }
        }
    }
    
    func removeAuthStateListener() {
        if let handle = authStateHandle {
            Auth.auth().removeStateDidChangeListener(handle)
        }
    }
    
    // メール・パスワードでユーザー登録
    func signUp(email: String, password: String) {
        guard !email.isEmpty, !password.isEmpty else {
            errorMessage = "メールアドレスとパスワードを入力してください"
            return
        }
        
        isLoading = true
        errorMessage = ""
        
        Auth.auth().createUser(withEmail: email, password: password) { [weak self] result, error in
            DispatchQueue.main.async {
                self?.isLoading = false
                
                if let error = error {
                    self?.handleAuthError(error)
                    return
                }
                
                // 登録成功後にメール認証を送信
                self?.sendEmailVerification()
            }
        }
    }
    
    // メール・パスワードでサインイン
    func signIn(email: String, password: String) {
        guard !email.isEmpty, !password.isEmpty else {
            errorMessage = "メールアドレスとパスワードを入力してください"
            return
        }
        
        isLoading = true
        errorMessage = ""
        
        Auth.auth().signIn(withEmail: email, password: password) { [weak self] result, error in
            DispatchQueue.main.async {
                self?.isLoading = false
                
                if let error = error {
                    self?.handleAuthError(error)
                    return
                }
                
                // サインイン成功
                print("サインイン成功: \(result?.user.email ?? "")")
            }
        }
    }
    
    // メール認証の送信
    func sendEmailVerification() {
        guard let user = Auth.auth().currentUser else { return }
        
        user.sendEmailVerification { [weak self] error in
            DispatchQueue.main.async {
                if let error = error {
                    self?.errorMessage = "メール認証の送信に失敗しました: \(error.localizedDescription)"
                } else {
                    self?.errorMessage = "認証メールを送信しました。メールを確認してください。"
                }
            }
        }
    }
    
    // パスワードリセット
    func resetPassword(email: String) {
        guard !email.isEmpty else {
            errorMessage = "メールアドレスを入力してください"
            return
        }
        
        Auth.auth().sendPasswordReset(withEmail: email) { [weak self] error in
            DispatchQueue.main.async {
                if let error = error {
                    self?.handleAuthError(error)
                } else {
                    self?.errorMessage = "パスワードリセットメールを送信しました"
                }
            }
        }
    }
    
    // サインアウト
    func signOut() {
        do {
            try Auth.auth().signOut()
        } catch {
            errorMessage = "サインアウトに失敗しました: \(error.localizedDescription)"
        }
    }
    
    // エラーハンドリング
    private func handleAuthError(_ error: Error) {
        if let authError = error as NSError?, 
           let errorCode = AuthErrorCode(rawValue: authError.code) {
            switch errorCode {
            case .emailAlreadyInUse:
                errorMessage = "このメールアドレスは既に使用されています"
            case .weakPassword:
                errorMessage = "パスワードが弱すぎます"
            case .invalidEmail:
                errorMessage = "無効なメールアドレスです"
            case .userNotFound:
                errorMessage = "ユーザーが見つかりません"
            case .wrongPassword:
                errorMessage = "パスワードが間違っています"
            case .userDisabled:
                errorMessage = "このアカウントは無効化されています"
            case .networkError:
                errorMessage = "ネットワークエラーが発生しました"
            default:
                errorMessage = "認証エラー: \(error.localizedDescription)"
            }
        } else {
            errorMessage = "予期しないエラー: \(error.localizedDescription)"
        }
    }
}

// SwiftUI認証画面の実装
// LoginView.swift
import SwiftUI

struct LoginView: View {
    @StateObject private var authViewModel = AuthenticationViewModel()
    @State private var email = ""
    @State private var password = ""
    @State private var isSignUpMode = false
    
    var body: some View {
        NavigationView {
            VStack(spacing: 20) {
                // ヘッダー
                VStack {
                    Image(systemName: "lock.shield")
                        .font(.system(size: 60))
                        .foregroundColor(.blue)
                    
                    Text(isSignUpMode ? "アカウント作成" : "ログイン")
                        .font(.largeTitle)
                        .fontWeight(.bold)
                }
                .padding(.bottom, 30)
                
                // フォーム
                VStack(spacing: 15) {
                    TextField("メールアドレス", text: $email)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                        .keyboardType(.emailAddress)
                        .autocapitalization(.none)
                    
                    SecureField("パスワード", text: $password)
                        .textFieldStyle(RoundedBorderTextFieldStyle())
                    
                    if !authViewModel.errorMessage.isEmpty {
                        Text(authViewModel.errorMessage)
                            .foregroundColor(.red)
                            .font(.caption)
                            .multilineTextAlignment(.center)
                    }
                }
                
                // アクションボタン
                VStack(spacing: 10) {
                    Button(action: {
                        if isSignUpMode {
                            authViewModel.signUp(email: email, password: password)
                        } else {
                            authViewModel.signIn(email: email, password: password)
                        }
                    }) {
                        if authViewModel.isLoading {
                            ProgressView()
                                .scaleEffect(0.8)
                                .progressViewStyle(CircularProgressViewStyle(tint: .white))
                        } else {
                            Text(isSignUpMode ? "アカウント作成" : "ログイン")
                        }
                    }
                    .frame(maxWidth: .infinity)
                    .frame(height: 50)
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
                    .disabled(authViewModel.isLoading)
                    
                    Button(action: {
                        isSignUpMode.toggle()
                        authViewModel.errorMessage = ""
                    }) {
                        Text(isSignUpMode ? "既にアカウントをお持ちですか?ログイン" : "アカウントをお持ちでない方は新規作成")
                            .font(.caption)
                    }
                    
                    if !isSignUpMode {
                        Button("パスワードを忘れた場合") {
                            authViewModel.resetPassword(email: email)
                        }
                        .font(.caption)
                        .foregroundColor(.gray)
                    }
                }
                
                Spacer()
            }
            .padding()
            .navigationBarHidden(true)
        }
    }
}

OAuth認証(Apple、Google等)の実装

// OAuth認証のセットアップ
// OAuthAuthenticationViewModel.swift
import Foundation
import Firebase
import AuthenticationServices
import CryptoKit

class OAuthAuthenticationViewModel: NSObject, ObservableObject {
    @Published var user: User?
    @Published var isLoading = false
    @Published var errorMessage = ""
    
    // Apple Sign In用のnonceを生成
    private func randomNonceString(length: Int = 32) -> String {
        precondition(length > 0)
        let charset: [Character] = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
        var result = ""
        var remainingLength = length
        
        while remainingLength > 0 {
            let randoms: [UInt8] = (0 ..< 16).map { _ in
                var random: UInt8 = 0
                let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
                if errorCode != errSecSuccess {
                    fatalError("Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)")
                }
                return random
            }
            
            randoms.forEach { random in
                if remainingLength == 0 {
                    return
                }
                
                if random < charset.count {
                    result.append(charset[Int(random)])
                    remainingLength -= 1
                }
            }
        }
        
        return result
    }
    
    // SHA256でハッシュ化
    private func sha256(_ input: String) -> String {
        let inputData = Data(input.utf8)
        let hashedData = SHA256.hash(data: inputData)
        let hashString = hashedData.compactMap {
            String(format: "%02x", $0)
        }.joined()
        
        return hashString
    }
    
    // Apple Sign In実装
    func signInWithApple() {
        let nonce = randomNonceString()
        let appleIDProvider = ASAuthorizationAppleIDProvider()
        let request = appleIDProvider.createRequest()
        request.requestedScopes = [.fullName, .email]
        request.nonce = sha256(nonce)
        
        let authorizationController = ASAuthorizationController(authorizationRequests: [request])
        authorizationController.delegate = self
        authorizationController.presentationContextProvider = self
        authorizationController.performRequests()
        
        // nonceを一時保存
        UserDefaults.standard.set(nonce, forKey: "currentNonce")
    }
    
    // Google Sign In実装(Google SDK が必要)
    func signInWithGoogle() {
        // Google Sign In SDK の実装
        // pod 'GoogleSignIn' を追加してから使用
        /*
        guard let clientID = FirebaseApp.app()?.options.clientID else { return }
        
        let config = GIDConfiguration(clientID: clientID)
        GIDSignIn.sharedInstance.configuration = config
        
        guard let presentingViewController = UIApplication.shared.windows.first?.rootViewController else {
            return
        }
        
        GIDSignIn.sharedInstance.signIn(withPresenting: presentingViewController) { [weak self] result, error in
            if let error = error {
                self?.errorMessage = "Google Sign In failed: \(error.localizedDescription)"
                return
            }
            
            guard let user = result?.user,
                  let idToken = user.idToken?.tokenString else {
                return
            }
            
            let credential = GoogleAuthProvider.credential(withIDToken: idToken,
                                                         accessToken: user.accessToken.tokenString)
            
            Auth.auth().signIn(with: credential) { authResult, error in
                if let error = error {
                    self?.errorMessage = "Firebase sign in failed: \(error.localizedDescription)"
                } else {
                    self?.errorMessage = "Google Sign In successful"
                }
            }
        }
        */
    }
    
    // Facebook Sign In実装(Facebook SDK が必要)
    func signInWithFacebook() {
        // Facebook SDK の実装
        // pod 'FBSDKLoginKit' を追加してから使用
        /*
        let loginManager = LoginManager()
        loginManager.logIn(permissions: ["email"], from: nil) { [weak self] result, error in
            if let error = error {
                self?.errorMessage = "Facebook Sign In failed: \(error.localizedDescription)"
                return
            }
            
            guard let accessToken = AccessToken.current else {
                self?.errorMessage = "Failed to get access token"
                return
            }
            
            let credential = FacebookAuthProvider.credential(withAccessToken: accessToken.tokenString)
            
            Auth.auth().signIn(with: credential) { authResult, error in
                if let error = error {
                    self?.errorMessage = "Firebase sign in failed: \(error.localizedDescription)"
                } else {
                    self?.errorMessage = "Facebook Sign In successful"
                }
            }
        }
        */
    }
    
    // 匿名サインイン
    func signInAnonymously() {
        isLoading = true
        
        Auth.auth().signInAnonymously { [weak self] authResult, error in
            DispatchQueue.main.async {
                self?.isLoading = false
                
                if let error = error {
                    self?.errorMessage = "匿名サインインに失敗しました: \(error.localizedDescription)"
                } else {
                    self?.errorMessage = "匿名サインインに成功しました"
                }
            }
        }
    }
}

// Apple Sign In デリゲート実装
extension OAuthAuthenticationViewModel: ASAuthorizationControllerDelegate {
    func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
            guard let nonce = UserDefaults.standard.string(forKey: "currentNonce") else {
                fatalError("Invalid state: A login callback was received, but no login request was sent.")
            }
            
            guard let appleIDToken = appleIDCredential.identityToken else {
                print("Unable to fetch identity token")
                return
            }
            
            guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
                print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
                return
            }
            
            let credential = OAuthProvider.credential(withProviderID: "apple.com",
                                                    idToken: idTokenString,
                                                    rawNonce: nonce)
            
            Auth.auth().signIn(with: credential) { [weak self] authResult, error in
                DispatchQueue.main.async {
                    if let error = error {
                        self?.errorMessage = "Apple Sign In failed: \(error.localizedDescription)"
                    } else {
                        self?.errorMessage = "Apple Sign In successful"
                        // ユーザー情報を更新(初回のみ)
                        if let user = authResult?.user, user.displayName == nil {
                            self?.updateUserProfile(user: user, credential: appleIDCredential)
                        }
                    }
                }
            }
        }
    }
    
    func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        DispatchQueue.main.async {
            self.errorMessage = "Apple Sign In error: \(error.localizedDescription)"
        }
    }
    
    private func updateUserProfile(user: User, credential: ASAuthorizationAppleIDCredential) {
        let changeRequest = user.createProfileChangeRequest()
        
        if let fullName = credential.fullName {
            let displayName = [fullName.givenName, fullName.familyName]
                .compactMap { $0 }
                .joined(separator: " ")
            
            if !displayName.isEmpty {
                changeRequest.displayName = displayName
            }
        }
        
        changeRequest.commitChanges { error in
            if let error = error {
                print("Failed to update user profile: \(error.localizedDescription)")
            }
        }
    }
}

// ASAuthorizationControllerPresentationContextProviding実装
extension OAuthAuthenticationViewModel: ASAuthorizationControllerPresentationContextProviding {
    func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
        return UIApplication.shared.windows.first { $0.isKeyWindow } ?? UIWindow()
    }
}

// SwiftUI OAuth認証ボタン
struct OAuthButtonsView: View {
    @StateObject private var oauthViewModel = OAuthAuthenticationViewModel()
    
    var body: some View {
        VStack(spacing: 15) {
            // Apple Sign In ボタン
            Button(action: {
                oauthViewModel.signInWithApple()
            }) {
                HStack {
                    Image(systemName: "applelogo")
                    Text("Appleでサインイン")
                }
                .frame(maxWidth: .infinity)
                .frame(height: 50)
                .background(Color.black)
                .foregroundColor(.white)
                .cornerRadius(10)
            }
            
            // Google Sign In ボタン
            Button(action: {
                oauthViewModel.signInWithGoogle()
            }) {
                HStack {
                    Image(systemName: "globe")
                    Text("Googleでサインイン")
                }
                .frame(maxWidth: .infinity)
                .frame(height: 50)
                .background(Color.red)
                .foregroundColor(.white)
                .cornerRadius(10)
            }
            
            // Facebook Sign In ボタン
            Button(action: {
                oauthViewModel.signInWithFacebook()
            }) {
                HStack {
                    Image(systemName: "f.circle")
                    Text("Facebookでサインイン")
                }
                .frame(maxWidth: .infinity)
                .frame(height: 50)
                .background(Color.blue)
                .foregroundColor(.white)
                .cornerRadius(10)
            }
            
            // 匿名サインイン ボタン
            Button(action: {
                oauthViewModel.signInAnonymously()
            }) {
                HStack {
                    Image(systemName: "person.circle")
                    Text("匿名でサインイン")
                }
                .frame(maxWidth: .infinity)
                .frame(height: 50)
                .background(Color.gray)
                .foregroundColor(.white)
                .cornerRadius(10)
            }
            
            if !oauthViewModel.errorMessage.isEmpty {
                Text(oauthViewModel.errorMessage)
                    .foregroundColor(.red)
                    .font(.caption)
                    .multilineTextAlignment(.center)
            }
        }
        .padding()
    }
}

ユーザー情報管理とプロファイル更新

// UserProfileViewModel.swift - ユーザープロファイル管理
import Foundation
import Firebase
import Combine

class UserProfileViewModel: ObservableObject {
    @Published var user: User?
    @Published var displayName = ""
    @Published var email = ""
    @Published var isEmailVerified = false
    @Published var isLoading = false
    @Published var message = ""
    
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        // 認証状態を監視
        Auth.auth().addStateDidChangeListener { [weak self] _, user in
            DispatchQueue.main.async {
                self?.user = user
                self?.loadUserData()
            }
        }
    }
    
    private func loadUserData() {
        guard let user = user else { return }
        
        displayName = user.displayName ?? ""
        email = user.email ?? ""
        isEmailVerified = user.isEmailVerified
    }
    
    // プロフィール更新
    func updateProfile() {
        guard let user = user else { return }
        
        isLoading = true
        
        let changeRequest = user.createProfileChangeRequest()
        changeRequest.displayName = displayName.isEmpty ? nil : displayName
        
        changeRequest.commitChanges { [weak self] error in
            DispatchQueue.main.async {
                self?.isLoading = false
                
                if let error = error {
                    self?.message = "プロフィール更新に失敗しました: \(error.localizedDescription)"
                } else {
                    self?.message = "プロフィールを更新しました"
                }
            }
        }
    }
    
    // パスワード変更
    func changePassword(currentPassword: String, newPassword: String) {
        guard let user = user, let email = user.email else { return }
        
        isLoading = true
        
        // 再認証が必要
        let credential = EmailAuthProvider.credential(withEmail: email, password: currentPassword)
        
        user.reauthenticate(with: credential) { [weak self] _, error in
            if let error = error {
                DispatchQueue.main.async {
                    self?.isLoading = false
                    self?.message = "現在のパスワードが間違っています: \(error.localizedDescription)"
                }
                return
            }
            
            // パスワード更新
            user.updatePassword(to: newPassword) { error in
                DispatchQueue.main.async {
                    self?.isLoading = false
                    
                    if let error = error {
                        self?.message = "パスワード変更に失敗しました: \(error.localizedDescription)"
                    } else {
                        self?.message = "パスワードを変更しました"
                    }
                }
            }
        }
    }
    
    // メール認証送信
    func sendEmailVerification() {
        guard let user = user else { return }
        
        user.sendEmailVerification { [weak self] error in
            DispatchQueue.main.async {
                if let error = error {
                    self?.message = "認証メール送信に失敗しました: \(error.localizedDescription)"
                } else {
                    self?.message = "認証メールを送信しました"
                }
            }
        }
    }
    
    // アカウント削除
    func deleteAccount(password: String, completion: @escaping (Bool) -> Void) {
        guard let user = user, let email = user.email else {
            completion(false)
            return
        }
        
        isLoading = true
        
        // 再認証が必要
        let credential = EmailAuthProvider.credential(withEmail: email, password: password)
        
        user.reauthenticate(with: credential) { [weak self] _, error in
            if let error = error {
                DispatchQueue.main.async {
                    self?.isLoading = false
                    self?.message = "認証に失敗しました: \(error.localizedDescription)"
                    completion(false)
                }
                return
            }
            
            // アカウント削除
            user.delete { error in
                DispatchQueue.main.async {
                    self?.isLoading = false
                    
                    if let error = error {
                        self?.message = "アカウント削除に失敗しました: \(error.localizedDescription)"
                        completion(false)
                    } else {
                        self?.message = "アカウントを削除しました"
                        completion(true)
                    }
                }
            }
        }
    }
    
    // サインアウト
    func signOut() {
        do {
            try Auth.auth().signOut()
            message = "サインアウトしました"
        } catch {
            message = "サインアウトに失敗しました: \(error.localizedDescription)"
        }
    }
}

// SwiftUI ユーザープロファイル画面
struct UserProfileView: View {
    @StateObject private var profileViewModel = UserProfileViewModel()
    @State private var currentPassword = ""
    @State private var newPassword = ""
    @State private var showingDeleteConfirmation = false
    @State private var deletePassword = ""
    
    var body: some View {
        NavigationView {
            ScrollView {
                VStack(spacing: 20) {
                    // ユーザー情報表示
                    VStack(spacing: 15) {
                        // プロフィール画像(仮)
                        Image(systemName: "person.circle.fill")
                            .font(.system(size: 80))
                            .foregroundColor(.gray)
                        
                        // 基本情報
                        VStack(spacing: 10) {
                            HStack {
                                Text("メール:")
                                    .fontWeight(.semibold)
                                Spacer()
                                Text(profileViewModel.email)
                                    .foregroundColor(.gray)
                            }
                            
                            HStack {
                                Text("認証状態:")
                                    .fontWeight(.semibold)
                                Spacer()
                                HStack {
                                    Image(systemName: profileViewModel.isEmailVerified ? "checkmark.circle.fill" : "xmark.circle.fill")
                                        .foregroundColor(profileViewModel.isEmailVerified ? .green : .red)
                                    Text(profileViewModel.isEmailVerified ? "認証済み" : "未認証")
                                        .foregroundColor(profileViewModel.isEmailVerified ? .green : .red)
                                }
                            }
                            
                            if !profileViewModel.isEmailVerified {
                                Button("認証メール送信") {
                                    profileViewModel.sendEmailVerification()
                                }
                                .font(.caption)
                                .foregroundColor(.blue)
                            }
                        }
                    }
                    .padding()
                    .background(Color(.systemGray6))
                    .cornerRadius(10)
                    
                    // 表示名更新
                    VStack(alignment: .leading, spacing: 10) {
                        Text("表示名")
                            .fontWeight(.semibold)
                        
                        TextField("表示名", text: $profileViewModel.displayName)
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                        
                        Button(action: {
                            profileViewModel.updateProfile()
                        }) {
                            Text("プロフィール更新")
                                .frame(maxWidth: .infinity)
                                .frame(height: 44)
                                .background(Color.blue)
                                .foregroundColor(.white)
                                .cornerRadius(8)
                        }
                        .disabled(profileViewModel.isLoading)
                    }
                    .padding()
                    .background(Color(.systemGray6))
                    .cornerRadius(10)
                    
                    // パスワード変更
                    VStack(alignment: .leading, spacing: 10) {
                        Text("パスワード変更")
                            .fontWeight(.semibold)
                        
                        SecureField("現在のパスワード", text: $currentPassword)
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                        
                        SecureField("新しいパスワード", text: $newPassword)
                            .textFieldStyle(RoundedBorderTextFieldStyle())
                        
                        Button(action: {
                            profileViewModel.changePassword(currentPassword: currentPassword, newPassword: newPassword)
                            currentPassword = ""
                            newPassword = ""
                        }) {
                            Text("パスワード変更")
                                .frame(maxWidth: .infinity)
                                .frame(height: 44)
                                .background(Color.orange)
                                .foregroundColor(.white)
                                .cornerRadius(8)
                        }
                        .disabled(profileViewModel.isLoading || currentPassword.isEmpty || newPassword.isEmpty)
                    }
                    .padding()
                    .background(Color(.systemGray6))
                    .cornerRadius(10)
                    
                    // 危険な操作
                    VStack(alignment: .leading, spacing: 15) {
                        Text("危険な操作")
                            .fontWeight(.semibold)
                            .foregroundColor(.red)
                        
                        Button(action: {
                            profileViewModel.signOut()
                        }) {
                            Text("サインアウト")
                                .frame(maxWidth: .infinity)
                                .frame(height: 44)
                                .background(Color.gray)
                                .foregroundColor(.white)
                                .cornerRadius(8)
                        }
                        
                        Button(action: {
                            showingDeleteConfirmation = true
                        }) {
                            Text("アカウント削除")
                                .frame(maxWidth: .infinity)
                                .frame(height: 44)
                                .background(Color.red)
                                .foregroundColor(.white)
                                .cornerRadius(8)
                        }
                    }
                    .padding()
                    .background(Color(.systemGray6))
                    .cornerRadius(10)
                    
                    // メッセージ表示
                    if !profileViewModel.message.isEmpty {
                        Text(profileViewModel.message)
                            .foregroundColor(.blue)
                            .font(.caption)
                            .multilineTextAlignment(.center)
                            .padding()
                    }
                }
                .padding()
            }
            .navigationTitle("プロフィール")
            .alert("アカウント削除", isPresented: $showingDeleteConfirmation) {
                SecureField("パスワード", text: $deletePassword)
                Button("削除", role: .destructive) {
                    profileViewModel.deleteAccount(password: deletePassword) { success in
                        if success {
                            // 削除成功時の処理
                        }
                    }
                    deletePassword = ""
                }
                Button("キャンセル", role: .cancel) {
                    deletePassword = ""
                }
            } message: {
                Text("アカウントを削除すると、すべてのデータが失われます。この操作は取り消せません。パスワードを入力して確認してください。")
            }
        }
    }
}

高度な認証機能とセキュリティ

// AdvancedAuthenticationService.swift - 高度な認証機能
import Foundation
import Firebase
import LocalAuthentication

class AdvancedAuthenticationService: ObservableObject {
    @Published var user: User?
    @Published var isLoading = false
    @Published var errorMessage = ""
    
    // カスタムクレームの取得
    func getCustomClaims() async throws -> [String: Any]? {
        guard let user = Auth.auth().currentUser else {
            throw AuthError.userNotFound
        }
        
        let result = try await user.getIDTokenResult()
        return result.claims
    }
    
    // IDトークンの取得
    func getIDToken(forceRefresh: Bool = false) async throws -> String {
        guard let user = Auth.auth().currentUser else {
            throw AuthError.userNotFound
        }
        
        return try await user.getIDToken(forcingRefresh: forceRefresh)
    }
    
    // 多要素認証の設定
    func enrollMultiFactor(phoneNumber: String) async throws {
        guard let user = Auth.auth().currentUser else {
            throw AuthError.userNotFound
        }
        
        let phoneCredential = PhoneAuthProvider.provider().credential(
            withVerificationID: "verification_id",
            verificationCode: "verification_code"
        )
        
        let assertion = PhoneMultiFactorGenerator.assertion(with: phoneCredential)
        
        try await user.multiFactor.enroll(with: assertion, displayName: "電話番号")
    }
    
    // 生体認証(Face ID / Touch ID)の確認
    func authenticateWithBiometrics() async throws -> Bool {
        let context = LAContext()
        var error: NSError?
        
        // 生体認証の利用可能性を確認
        guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
            throw BiometricError.notAvailable
        }
        
        do {
            let reason = "アプリにアクセスするために生体認証を使用します"
            return try await context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason)
        } catch {
            throw BiometricError.failed
        }
    }
    
    // カスタムトークンでの認証
    func signInWithCustomToken(_ token: String) async throws {
        do {
            let result = try await Auth.auth().signIn(withCustomToken: token)
            DispatchQueue.main.async {
                self.user = result.user
            }
        } catch {
            throw error
        }
    }
    
    // アカウントリンク
    func linkAccount(with credential: AuthCredential) async throws {
        guard let user = Auth.auth().currentUser else {
            throw AuthError.userNotFound
        }
        
        do {
            let result = try await user.link(with: credential)
            DispatchQueue.main.async {
                self.user = result.user
            }
        } catch {
            throw error
        }
    }
    
    // プロバイダーの解除
    func unlinkProvider(_ providerID: String) async throws {
        guard let user = Auth.auth().currentUser else {
            throw AuthError.userNotFound
        }
        
        do {
            let result = try await user.unlink(fromProvider: providerID)
            DispatchQueue.main.async {
                self.user = result.user
            }
        } catch {
            throw error
        }
    }
    
    // セキュリティイベントの監視
    func monitorSecurityEvents() {
        Auth.auth().addIDTokenDidChangeListener { [weak self] _, user in
            if let user = user {
                // IDトークンが変更された時の処理
                print("ID token changed for user: \(user.uid)")
                
                // セキュリティログの記録
                Task {
                    await self?.logSecurityEvent("id_token_changed", userID: user.uid)
                }
            }
        }
    }
    
    private func logSecurityEvent(_ event: String, userID: String) async {
        // セキュリティイベントのログ記録
        // Firestore や Cloud Functions を使用してログを保存
        print("Security event: \(event) for user: \(userID)")
    }
}

// カスタムエラー定義
enum AuthError: Error, LocalizedError {
    case userNotFound
    case tokenExpired
    case networkError
    
    var errorDescription: String? {
        switch self {
        case .userNotFound:
            return "ユーザーが見つかりません"
        case .tokenExpired:
            return "トークンの有効期限が切れています"
        case .networkError:
            return "ネットワークエラーが発生しました"
        }
    }
}

enum BiometricError: Error, LocalizedError {
    case notAvailable
    case failed
    
    var errorDescription: String? {
        switch self {
        case .notAvailable:
            return "生体認証は利用できません"
        case .failed:
            return "生体認証に失敗しました"
        }
    }
}

// SecureAuthenticationView.swift - セキュア認証画面
struct SecureAuthenticationView: View {
    @StateObject private var authService = AdvancedAuthenticationService()
    @State private var showingBiometricPrompt = false
    @State private var customToken = ""
    
    var body: some View {
        VStack(spacing: 20) {
            Text("高度な認証機能")
                .font(.title)
                .fontWeight(.bold)
            
            // 生体認証ボタン
            Button(action: {
                Task {
                    do {
                        let success = try await authService.authenticateWithBiometrics()
                        if success {
                            print("生体認証成功")
                        }
                    } catch {
                        authService.errorMessage = error.localizedDescription
                    }
                }
            }) {
                HStack {
                    Image(systemName: "faceid")
                    Text("Face ID / Touch ID で認証")
                }
                .frame(maxWidth: .infinity)
                .frame(height: 50)
                .background(Color.green)
                .foregroundColor(.white)
                .cornerRadius(10)
            }
            
            // カスタムトークン認証
            VStack(spacing: 10) {
                TextField("カスタムトークン", text: $customToken)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                
                Button(action: {
                    Task {
                        do {
                            try await authService.signInWithCustomToken(customToken)
                        } catch {
                            authService.errorMessage = error.localizedDescription
                        }
                    }
                }) {
                    Text("カスタムトークンで認証")
                        .frame(maxWidth: .infinity)
                        .frame(height: 44)
                        .background(Color.purple)
                        .foregroundColor(.white)
                        .cornerRadius(8)
                }
                .disabled(customToken.isEmpty)
            }
            
            // IDトークン取得
            Button(action: {
                Task {
                    do {
                        let token = try await authService.getIDToken(forceRefresh: true)
                        print("ID Token: \(token)")
                    } catch {
                        authService.errorMessage = error.localizedDescription
                    }
                }
            }) {
                Text("IDトークン取得")
                    .frame(maxWidth: .infinity)
                    .frame(height: 44)
                    .background(Color.indigo)
                    .foregroundColor(.white)
                    .cornerRadius(8)
            }
            
            // カスタムクレーム取得
            Button(action: {
                Task {
                    do {
                        let claims = try await authService.getCustomClaims()
                        print("Custom Claims: \(claims ?? [:])")
                    } catch {
                        authService.errorMessage = error.localizedDescription
                    }
                }
            }) {
                Text("カスタムクレーム取得")
                    .frame(maxWidth: .infinity)
                    .frame(height: 44)
                    .background(Color.teal)
                    .foregroundColor(.white)
                    .cornerRadius(8)
            }
            
            if !authService.errorMessage.isEmpty {
                Text(authService.errorMessage)
                    .foregroundColor(.red)
                    .font(.caption)
                    .multilineTextAlignment(.center)
            }
            
            Spacer()
        }
        .padding()
        .onAppear {
            authService.monitorSecurityEvents()
        }
    }
}