FirebaseIdentity 2.0.0

FirebaseIdentity 2.0.0

Christian R. Gossain 维护。



 
依赖
Firebase/Auth>= 0
Firebase/Core>= 0
ProcedureKit>= 0
 

  • 作者
  • cgossain

FirebaseIdentity

CI Status Version License Platform

简介

此库的目的是为了使那些不想使用 FirebaseUI 库的人们在 iOS 上更轻松地构建围绕 Firebase 身份验证服务的自定义前端 UI。它通过实现标准的身份验证工作流和错误处理来实现这一点(例如账户链接、个人资料更新、设置/更新密码、重新认证、启用/禁用第三方提供商、账户删除、自动重试等)。这是通过将大量的错误处理逻辑抽象化到一个单例状态机('AuthManager')中,该状态机能够处理大多数 Firebase 身份验证用例来完成的。

使用方式

AuthManager 单例

您需要主要与之交互的类是 AuthManager

设置应在应用程序的生命周期早期完成,但在初始化 Firebase 库之后。

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    ...
    
    // firebase
    FirebaseApp.configure()

    // facebook
    ApplicationDelegate.shared.application(application, didFinishLaunchingWithOptions: launchOptions)
    
    // configure other auth providers as needed
    ...

    // auth manager
    AuthManager.configure()

    // observer auth state
    // the auth manager state changes as the Firebase authentication state changes; by listening to this state change 
    // you can appropriately update your UI
    authenticationStateObserver =
        NotificationCenter.default.addObserver(forName: AuthManager.authenticationStateChangedNotification, object: nil, queue: .main, using: { (note) in
            guard let manager = note.object as? AuthManager else {
                return
            }

            switch manager.authenticationState {
            case .authenticated:
                AppController.login()
            case .notDetermined, .notAuthenticated:
                AppController.logout()
            }
        })
        
    ...
    
    return true
}

身份提供者

身份提供者是通过 IdentityProvider 类来建模的。

目前仅实现了 EmailIdentityProviderFacebookIdentityProvider,但您很轻松地可以通过继承 IdentityProvider 来实现其他提供者。如果您实现了其他的,请随时提交一个拉取请求 :)。

使用电子邮件身份验证注册新用户

let provider = EmailIdentityProvider(email: user.email, password: user.password)
AuthManager.shared.signUp(with: provider) { (result) in
    switch result {
    case .success(let value):
        print(value)

    case .failure(let error):
        self.showAuthenticationErrorAlert(for: error)
    }
}

使用 Facebook 身份验证注册/登录用户

let requestedPermissions: [String] = ["email"]
self.fbLoginManager.logIn(permissions: requestedPermissions, from: self) { (result, error) in
    guard let result = result, !result.isCancelled else {
        if let error = error {
            print(error.localizedDescription)
            self.showAlert(for: error)
        }
        return
    }

    let token = AccessToken.current!.tokenString
    let provider = FaceboookIdentityProvider(accessToken: token)
    AuthManager.shared.signUp(with: provider) { (result) in
        switch result {
        case .success(let value):
            print(value)

        case .failure(let error):
            self.showAuthenticationErrorAlert(for: error)
        }
    }
}

示例应用程序中包含了许多其他示例,您应该查看这些示例。

重新认证

Firebase 中有一些用户资料变更需要最近的一次登录。尤其是,Firebase 在这种情况下会触发 FIRAuthErrorCodeRequiresRecentLogin = 17014, 错误。

此库通过 AuthManagerReauthenticating 协议来处理此错误。

但作为额外的奖励,此库还跟踪最后一次用户登录时间,如果在最后一次登录之后超过了5分钟(5分钟相当于当前记录的 Firebase 最近登录阈值),则会乐观地请求重新认证。在本地跟踪此信息的益处在于,我们可以避免生成此错误所需的额外网络请求。

重新认证只在用户已经登录时生效,因此实现此协议的一个好地方是在提供账户资料变更 UI 的视图控制器上。例如,

extension AccountViewController: AuthManagerReauthenticating {
    func authManager(_ manager: AuthManager, reauthenticateUsing providers: [IdentityProviderUserInfo], challenge: ProfileChangeReauthenticationChallenge) {
        // ask for reauthentication from the highest priority auth provider
        guard let provider = providers.first else {
            return
        }

        switch provider.providerID {
        case .email:
            // an email provider will always have an email associated with it, therefore it should be safe to force unwrap this value here;
            // what if there is some kind of error that causes the email to be non-existant in this scenario? Force the user to log-out, then back in?
            // it seems like it would be impossible for the email to not exist on an email auth provider
            let email = provider.email!

            // present UI for user to provider their current password
            let alert = UIAlertController(title: "Confirm Password", message: "Your current password is required to change your email address.\n\nCurrent Email: \(email)\nTarget Email:\(challenge.context.profileChangeType.attemptedValue)", preferredStyle: .actionSheet)
            for password in Set(AuthManager.debugEmailProviderUsers.map({ $0.password })) {
                alert.addAction(UIAlertAction(title: password, style: .default, handler: { (action) in
                    let provider = EmailIdentityProvider(email: email, password: password)
                    manager.reauthenticate(with: provider, for: challenge) { (error) in
                        guard let error = error else {
                            return
                        }
                        self.showAuthenticationErrorAlert(for: error)
                    }
                }))
            }

            alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
            self.present(alert, animated: true, completion: nil)
        case .facebook:
            fetchFacebookAccessTokenForReauthentication { (token) in
                guard let token = token else {
                    return
                }
                let provider = FaceboookIdentityProvider(accessToken: token)
                manager.reauthenticate(with: provider, for: challenge) { (error) in
                    guard let error = error else {
                        return
                    }
                    self.showAuthenticationErrorAlert(for: error)
                }
            }
        default:
            print("undefined provider")
        }
    }
}

这已在包含的示例应用程序中的 SignedInViewController 中实现。

账户删除

您可能想让用户有能力删除自己的账户。

此库通过提供 DeleteAccountOperation 来简化这一过程。

删除用户的账户

...

// optionally provide database refs to delete (i.e. user scoped refs); these will be deleted before the account is deleted
let userRefs: [DatabaseReference]? = [userRef1, userRef2, ...]

// create a new op instance
let deleteAccountOp = DeleteAccountOperation(refs: userRefs)
deleteAccountOp.deleteAccountCompletionBlock = { [unowned self] (result) in
    DispatchQueue.main.async {
        switch result {
        case .success(let user):
            // post account deletion (i.e. show login screen, track event, etc.)

        case .failure(let error):
            switch error {
            case .cancelledByUser:
                break
            case .other(let msg):
                // show alert
            }
        }
    }
}

// pass the op to the AuthManager
AuthManager.shared.deleteAccount(with: deleteAccountOp)

...

示例

包含的示例项目展示了此库的功能。

先决条件

要运行示例项目,请首先克隆存储库,然后从示例目录运行pod install

要运行示例项目,您还需要一个Firebase账户。一旦创建账户,请按照说明创建一个演示项目。在Firebase账户中创建演示项目后,您需要下载项目相应的GoogleService-Info.plist并将其添加到演示项目中。在应用启动时,Firebase框架会自动检测此文件并根据环境进行配置。

需求

  • iOS 11.4+
  • Swift 5+
  • 一个Firebase账户

安装

FirebaseIdentity 可通过 CocoaPods 获取。要安装它,只需将以下行添加到您的 Podfile 中

pod 'FirebaseIdentity'

作者

cgossain, [email protected]

许可证

FirebaseIdentity 是在MIT许可证下可用的。有关更多信息,请参阅 LICENSE 文件。