在 iOS 上让用户通过 Facebook 登录
您可以将 Facebook 登录服务或 Facebook 受限登录服务集成到您的应用中,让您的用户能够使用自己的 Facebook 账号进行 Identity Platform 身份验证。本页面介绍如何使用 Identity Platform 将“使用 Facebook 账号登录”添加到 iOS 应用。
准备工作
在您的
Podfile
中添加以下 Pod:pod 'FirebaseAuth'
前往 Google Cloud 控制台中的身份提供商页面。
点击添加提供商。
从列表中选择 Facebook。
输入您的 Facebook 应用 ID 和应用密钥。如果您还没有 ID 和密钥,则可以从 Facebook for Developers 页面获取一个。
将配置 Facebook 下列出的 URI 配置为您的 Facebook 应用的有效 OAuth 重定向 URI。如果您在 Identity Platform 中配置了自定义网域,请更新 Facebook 应用配置中的重定向 URI,以使用自定义网域(而非默认网域)。例如,将
https://myproject.firebaseapp.com/__/auth/handler
更改为https://auth.myownpersonaldomain.com/__/auth/handler
。点击已获授权的网域下的添加网域,以注册您应用的网域。出于开发目的,
localhost
默认处于启用状态。在配置您的应用下,点击设置详情。将代码段复制到应用的代码中,以初始化 Identity Platform Client SDK。
点击保存。
实现 Facebook 登录
要使用“传统版”Facebook 登录,请完成以下步骤。或者,您也可以使用 Facebook 受限登录,如下一部分所示。
- 按开发者文档中的说明操作,将 Facebook 登录集成到您的应用中。当初始化
FBSDKLoginButton
对象时,设置一个委托 (delegate) 来接收登录和退出账号事件。例如:Swift
let loginButton = FBSDKLoginButton() loginButton.delegate = self
Objective-C
FBSDKLoginButton *loginButton = [[FBSDKLoginButton alloc] init]; loginButton.delegate = self;
didCompleteWithResult:error:
。Swift
func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) { if let error = error { print(error.localizedDescription) return } // ... }
Objective-C
- (void)loginButton:(FBSDKLoginButton *)loginButton didCompleteWithResult:(FBSDKLoginManagerLoginResult *)result error:(NSError *)error { if (error == nil) { // ... } else { NSLog(error.localizedDescription); } }
- 在
UIApplicationDelegate
中导入 Firebase 模块:Swift
import FirebaseCore import FirebaseAuth
Objective-C
@import FirebaseCore; @import FirebaseAuth;
- 配置一个
FirebaseApp
共享实例(通常在应用的application:didFinishLaunchingWithOptions:
方法中配置):Swift
// Use Firebase library to configure APIs FirebaseApp.configure()
Objective-C
// Use Firebase library to configure APIs [FIRApp configure];
- 用户成功登录后,在
didCompleteWithResult:error:
的实现代码中为登录的用户获取一个访问令牌,然后用该访问令牌换取 Identity Platform 凭据:Swift
let credential = FacebookAuthProvider .credential(withAccessToken: AccessToken.current!.tokenString)
Objective-C
FIRAuthCredential *credential = [FIRFacebookAuthProvider credentialWithAccessToken:[FBSDKAccessToken currentAccessToken].tokenString];
实现 Facebook 受限登录
要使用 Facebook 受限登录(而非“传统版”Facebook 登录),请完成以下步骤。
- 按开发者文档中的说明操作,将 Facebook 受限登录集成到您的应用中。
- 为每个登录请求生成唯一的随机字符串(“随机数”),用来确保您获取的 ID 令牌专门用于响应您的应用的身份验证请求。此步骤对于防止重放攻击至关重要。您可以在 iOS 上使用
SecRandomCopyBytes(_:_:_)
生成加密的安全随机数,如以下示例所示:Swift
private func randomNonceString(length: Int = 32) -> String { precondition(length > 0) var randomBytes = [UInt8](repeating: 0, count: length) let errorCode = SecRandomCopyBytes(kSecRandomDefault, randomBytes.count, &randomBytes) if errorCode != errSecSuccess { fatalError( "Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)" ) } let charset: [Character] = Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._") let nonce = randomBytes.map { byte in // Pick a random character from the set, wrapping around if needed. charset[Int(byte) % charset.count] } return String(nonce) }
Objective-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]; }
Swift
@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 }
Objective-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
,并附加一个随机数。例如:Swift
func setupLoginButton() { let nonce = randomNonceString() currentNonce = nonce loginButton.delegate = self loginButton.loginTracking = .limited loginButton.nonce = sha256(nonce) }
Objective-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:
。Swift
func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) { if let error = error { print(error.localizedDescription) return } // ... }
Objective-C
- (void)loginButton:(FBSDKLoginButton *)loginButton didCompleteWithResult:(FBSDKLoginManagerLoginResult *)result error:(NSError *)error { if (error == nil) { // ... } else { NSLog(error.localizedDescription); } }
- 在
UIApplicationDelegate
中导入 Firebase 模块:Swift
import Firebase
Objective-C
@import Firebase;
- 配置一个
FirebaseApp
共享实例(通常在应用的application:didFinishLaunchingWithOptions:
方法中配置):Swift
// Use Firebase library to configure APIs FirebaseApp.configure()
Objective-C
// Use Firebase library to configure APIs [FIRApp configure];
- 用户成功登录后,在
didCompleteWithResult:error:
实现中,使用 Facebook 响应中的 ID 令牌和未经哈希处理的随机数来获取 Identity Platform 凭据:Swift
// Initialize an Identity Platform credential. let idTokenString = AuthenticationToken.current?.tokenString let nonce = currentNonce let credential = OAuthProvider.credential(withProviderID: "facebook.com", IDToken: idTokenString, rawNonce: nonce)
Objective-C
// Initialize an Identity Platform credential. NSString *idTokenString = FBSDKAuthenticationToken.currentAuthenticationToken.tokenString; NSString *rawNonce = self.currentNonce; FIROAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"facebook.com" IDToken:idTokenString rawNonce:rawNonce];
使用 Identity Platform 进行身份验证
最后,使用 Identity Platform 凭据向 Identity Platform 进行身份验证:
Swift
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 // ... }
Objective-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; // ... }];
后续步骤
- 详细了解 Identity Platform 用户。
- 让用户通过其他身份提供商登录。