您可以通过将 Facebook 登录或 Facebook 有限登录集成到您的应用中,让您的用户使用他们的 Facebook 帐户通过 Firebase 进行身份验证。
在你开始之前
使用 Swift Package Manager 安装和管理 Firebase 依赖项。
- 在 Xcode 中,打开您的应用程序项目,导航至File > Add Packages 。
- 出现提示时,添加 Firebase Apple 平台 SDK 存储库:
- 选择 Firebase 身份验证库。
- 完成后,Xcode 将自动开始在后台解析和下载您的依赖项。
https://github.com/firebase/firebase-ios-sdk
接下来,执行一些配置步骤:
- 在Facebook for Developers网站上,为您的应用获取App ID和App Secret 。
- 启用 Facebook 登录:
- 在Firebase 控制台中,打开Auth部分。
- 在Sign in method选项卡上,启用Facebook登录方法并指定您从 Facebook 获得的App ID和App Secret 。
- 然后,确保您的OAuth 重定向 URI (例如
my-app-12345.firebaseapp.com/__/auth/handler
)在产品的Facebook for Developers站点上的 Facebook 应用程序设置页面中被列为您的OAuth 重定向 URI之一设置 > Facebook 登录配置。
实施 Facebook 登录
要使用“经典”Facebook 登录,请完成以下步骤。或者,您可以使用 Facebook Limited Login,如下一节所示。
- 按照开发人员的文档将 Facebook 登录集成到您的应用程序中。当您初始化
FBSDKLoginButton
对象时,设置一个委托来接收登录和注销事件。例如:在您的委托中,实现迅速
let loginButton = FBSDKLoginButton() loginButton.delegate = self
目标-C
FBSDKLoginButton *loginButton = [[FBSDKLoginButton alloc] init]; loginButton.delegate = self;
didCompleteWithResult:error:
。迅速
func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) { if let error = error { print(error.localizedDescription) return } // ... }
目标-C
- (void)loginButton:(FBSDKLoginButton *)loginButton didCompleteWithResult:(FBSDKLoginManagerLoginResult *)result error:(NSError *)error { if (error == nil) { // ... } else { NSLog(error.localizedDescription); } }
- 在您的
UIApplicationDelegate
中导入FirebaseCore
模块,以及您的应用委托使用的任何其他Firebase 模块。例如,要使用 Cloud Firestore 和身份验证:斯威夫特用户界面
import SwiftUI import FirebaseCore import FirebaseFirestore import FirebaseAuth // ...
迅速
import FirebaseCore import FirebaseFirestore import FirebaseAuth // ...
目标-C
@import FirebaseCore; @import FirebaseFirestore; @import FirebaseAuth; // ...
- 在您的应用委托的
application(_:didFinishLaunchingWithOptions:)
方法中配置一个FirebaseApp
共享实例:斯威夫特用户界面
// Use Firebase library to configure APIs FirebaseApp.configure()
迅速
// Use Firebase library to configure APIs FirebaseApp.configure()
目标-C
// Use Firebase library to configure APIs [FIRApp configure];
- 如果您使用的是 SwiftUI,则必须创建一个应用程序委托并通过
UIApplicationDelegateAdaptor
或NSApplicationDelegateAdaptor
将其附加到您的App
结构。您还必须禁用应用委托调配。有关详细信息,请参阅SwiftUI 说明。斯威夫特用户界面
@main struct YourApp: App { // register app delegate for Firebase setup @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate var body: some Scene { WindowGroup { NavigationView { ContentView() } } } }
- 用户成功登录后,在
didCompleteWithResult:error:
的实现中,获取登录用户的访问令牌并将其交换为 Firebase 凭证:迅速
let credential = FacebookAuthProvider .credential(withAccessToken: AccessToken.current!.tokenString)
目标-C
FIRAuthCredential *credential = [FIRFacebookAuthProvider credentialWithAccessToken:[FBSDKAccessToken currentAccessToken].tokenString];
实施 Facebook 有限登录
要使用 Facebook 有限登录而不是“经典”Facebook 登录,请完成以下步骤。
- 按照开发人员的文档将 Facebook Limited Login 集成到您的应用程序中。
- 对于每个登录请求,生成一个唯一的随机字符串——一个“nonce”——你将使用它来确保你获得的 ID 令牌是专门为响应你的应用程序的身份验证请求而授予的。此步骤对于防止重放攻击很重要。您可以使用
SecRandomCopyBytes(_:_:_)
生成加密安全随机数,如以下示例所示:您将随登录请求一起发送随机数的 SHA-256 哈希值,Facebook 将在响应中原封不动地传递该哈希值。 Firebase 通过散列原始随机数并将其与 Facebook 传递的值进行比较来验证响应。迅速
// Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-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 }
目标-C
// Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce - (NSString *)randomNonce:(NSInteger)length { NSAssert(length > 0, @"Expected nonce to have positive length"); NSString *characterSet = @"0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._"; NSMutableString *result = [NSMutableString string]; NSInteger remainingLength = length; while (remainingLength > 0) { NSMutableArray *randoms = [NSMutableArray arrayWithCapacity:16]; for (NSInteger i = 0; i < 16; i++) { uint8_t random = 0; int errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random); NSAssert(errorCode == errSecSuccess, @"Unable to generate nonce: OSStatus %i", errorCode); [randoms addObject:@(random)]; } for (NSNumber *random in randoms) { if (remainingLength == 0) { break; } if (random.unsignedIntValue < characterSet.length) { unichar character = [characterSet characterAtIndex:random.unsignedIntValue]; [result appendFormat:@"%C", character]; remainingLength--; } } } return [result copy]; }
迅速
@available(iOS 13, *) 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 }
目标-C
- (NSString *)stringBySha256HashingString:(NSString *)input { const char *string = [input UTF8String]; unsigned char result[CC_SHA256_DIGEST_LENGTH]; CC_SHA256(string, (CC_LONG)strlen(string), result); NSMutableString *hashed = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2]; for (NSInteger i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) { [hashed appendFormat:@"%02x", result[i]]; } return hashed; }
- 当您设置
FBSDKLoginButton
时,设置一个委托来接收登录和注销事件,将跟踪模式设置为FBSDKLoginTrackingLimited
,并附加一个随机数。例如:在您的委托中,实现迅速
func setupLoginButton() { let nonce = randomNonceString() currentNonce = nonce loginButton.delegate = self loginButton.loginTracking = .limited loginButton.nonce = sha256(nonce) }
目标-C
- (void)setupLoginButton { NSString *nonce = [self randomNonce:32]; self.currentNonce = nonce; self.loginButton.delegate = self; self.loginButton.loginTracking = FBSDKLoginTrackingLimited self.loginButton.nonce = [self stringBySha256HashingString:nonce]; }
didCompleteWithResult:error:
。迅速
func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) { if let error = error { print(error.localizedDescription) return } // ... }
目标-C
- (void)loginButton:(FBSDKLoginButton *)loginButton didCompleteWithResult:(FBSDKLoginManagerLoginResult *)result error:(NSError *)error { if (error == nil) { // ... } else { NSLog(error.localizedDescription); } }
- 在您的
UIApplicationDelegate
中导入FirebaseCore
模块,以及您的应用委托使用的任何其他Firebase 模块。例如,要使用 Cloud Firestore 和身份验证:斯威夫特用户界面
import SwiftUI import FirebaseCore import FirebaseFirestore import FirebaseAuth // ...
迅速
import FirebaseCore import FirebaseFirestore import FirebaseAuth // ...
目标-C
@import FirebaseCore; @import FirebaseFirestore; @import FirebaseAuth; // ...
- 在您的应用委托的
application(_:didFinishLaunchingWithOptions:)
方法中配置一个FirebaseApp
共享实例:斯威夫特用户界面
// Use Firebase library to configure APIs FirebaseApp.configure()
迅速
// Use Firebase library to configure APIs FirebaseApp.configure()
目标-C
// Use Firebase library to configure APIs [FIRApp configure];
- 如果您使用的是 SwiftUI,则必须创建一个应用程序委托并通过
UIApplicationDelegateAdaptor
或NSApplicationDelegateAdaptor
将其附加到您的App
结构。您还必须禁用应用委托调配。有关详细信息,请参阅SwiftUI 说明。斯威夫特用户界面
@main struct YourApp: App { // register app delegate for Firebase setup @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate var body: some Scene { WindowGroup { NavigationView { ContentView() } } } }
- 用户成功登录后,在
didCompleteWithResult:error:
的实现中,使用来自 Facebook 响应的 ID 令牌和未散列的随机数来获取 Firebase 凭证:迅速
// Initialize a Firebase credential. let idTokenString = AuthenticationToken.current?.tokenString let nonce = currentNonce let credential = OAuthProvider.credential(withProviderID: "facebook.com", idToken: idTokenString!, rawNonce: nonce)
目标-C
// Initialize a Firebase credential. NSString *idTokenString = FBSDKAuthenticationToken.currentAuthenticationToken.tokenString; NSString *rawNonce = self.currentNonce; FIROAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"facebook.com" IDToken:idTokenString rawNonce:rawNonce];
使用 Firebase 进行身份验证
最后,使用 Firebase 凭据通过 Firebase 进行身份验证:
迅速
Auth.auth().signIn(with: credential) { authResult, error in if let error = error { let authError = error as NSError if isMFAEnabled, authError.code == AuthErrorCode.secondFactorRequired.rawValue { // The user is a multi-factor user. Second factor challenge is required. let resolver = authError .userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver var displayNameString = "" for tmpFactorInfo in resolver.hints { displayNameString += tmpFactorInfo.displayName ?? "" displayNameString += " " } self.showTextInputPrompt( withMessage: "Select factor to sign in\n\(displayNameString)", completionBlock: { userPressedOK, displayName in var selectedHint: PhoneMultiFactorInfo? for tmpFactorInfo in resolver.hints { if displayName == tmpFactorInfo.displayName { selectedHint = tmpFactorInfo as? PhoneMultiFactorInfo } } PhoneAuthProvider.provider() .verifyPhoneNumber(with: selectedHint!, uiDelegate: nil, multiFactorSession: resolver .session) { verificationID, error in if error != nil { print( "Multi factor start sign in failed. Error: \(error.debugDescription)" ) } else { self.showTextInputPrompt( withMessage: "Verification code for \(selectedHint?.displayName ?? "")", completionBlock: { userPressedOK, verificationCode in let credential: PhoneAuthCredential? = PhoneAuthProvider.provider() .credential(withVerificationID: verificationID!, verificationCode: verificationCode!) let assertion: MultiFactorAssertion? = PhoneMultiFactorGenerator .assertion(with: credential!) resolver.resolveSignIn(with: assertion!) { authResult, error in if error != nil { print( "Multi factor finanlize sign in failed. Error: \(error.debugDescription)" ) } else { self.navigationController?.popViewController(animated: true) } } } ) } } } ) } else { self.showMessagePrompt(error.localizedDescription) return } // ... return } // User is signed in // ... }
目标-C
[[FIRAuth auth] signInWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { if (isMFAEnabled && error && error.code == FIRAuthErrorCodeSecondFactorRequired) { FIRMultiFactorResolver *resolver = error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey]; NSMutableString *displayNameString = [NSMutableString string]; for (FIRMultiFactorInfo *tmpFactorInfo in resolver.hints) { [displayNameString appendString:tmpFactorInfo.displayName]; [displayNameString appendString:@" "]; } [self showTextInputPromptWithMessage:[NSString stringWithFormat:@"Select factor to sign in\n%@", displayNameString] completionBlock:^(BOOL userPressedOK, NSString *_Nullable displayName) { FIRPhoneMultiFactorInfo* selectedHint; for (FIRMultiFactorInfo *tmpFactorInfo in resolver.hints) { if ([displayName isEqualToString:tmpFactorInfo.displayName]) { selectedHint = (FIRPhoneMultiFactorInfo *)tmpFactorInfo; } } [FIRPhoneAuthProvider.provider verifyPhoneNumberWithMultiFactorInfo:selectedHint UIDelegate:nil multiFactorSession:resolver.session completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) { if (error) { [self showMessagePrompt:error.localizedDescription]; } else { [self showTextInputPromptWithMessage:[NSString stringWithFormat:@"Verification code for %@", selectedHint.displayName] completionBlock:^(BOOL userPressedOK, NSString *_Nullable verificationCode) { FIRPhoneAuthCredential *credential = [[FIRPhoneAuthProvider provider] credentialWithVerificationID:verificationID verificationCode:verificationCode]; FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential]; [resolver resolveSignInWithAssertion:assertion completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { if (error) { [self showMessagePrompt:error.localizedDescription]; } else { NSLog(@"Multi factor finanlize sign in succeeded."); } }]; }]; } }]; }]; } else if (error) { // ... return; } // User successfully signed in. Get user data from the FIRUser object if (authResult == nil) { return; } FIRUser *user = authResult.user; // ... }];
下一步
用户首次登录后,将创建一个新的用户帐户并将其链接到用户登录所用的凭据,即用户名和密码、电话号码或身份验证提供商信息。这个新帐户存储为您的 Firebase 项目的一部分,可用于在项目中的每个应用程序中识别用户,无论用户如何登录。
在您的 Firebase Realtime Database 和 Cloud Storage Security Rules中,您可以从
auth
变量中获取登录用户的唯一用户 ID,并使用它来控制用户可以访问的数据。
您可以允许用户使用多个身份验证提供程序登录您的应用程序,方法是将身份验证提供程序凭据链接到现有用户帐户。
要注销用户,请调用signOut:
。
迅速
let firebaseAuth = Auth.auth() do { try firebaseAuth.signOut() } catch let signOutError as NSError { print("Error signing out: %@", signOutError) }
目标-C
NSError *signOutError; BOOL status = [[FIRAuth auth] signOut:&signOutError]; if (!status) { NSLog(@"Error signing out: %@", signOutError); return; }
您可能还想为所有身份验证错误添加错误处理代码。请参阅处理错误。