iOS에서 Facebook으로 사용자 로그인
Facebook 로그인 또는 Facebook Limited 로그인을 앱에 통합하여 사용자가 Facebook 계정을 사용하여 Identity Platform으로 인증하도록 할 수 있습니다. 이 페이지에서는 Identity Platform을 사용하여 iOS 앱에 Facebook으로 로그인을 추가하는 방법을 보여줍니다.
시작하기 전에
Podfile
에 다음 포드를 추가합니다.pod 'FirebaseAuth'
Google Cloud Console에서 ID 공급업체 페이지로 이동합니다.
공급업체 추가를 클릭합니다.
목록에서 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 클라이언트 SDK를 초기화합니다.
저장을 클릭합니다.
Facebook 로그인 구현
'기본' Facebook 로그인을 사용하려면 다음 단계를 완료하세요. 또는 다음 섹션에서와 같이 Facebook Limited 로그인을 사용할 수 있습니다.
- 개발자 문서를 참고해 앱에 Facebook 로그인을 통합합니다.
FBSDKLoginButton
객체를 초기화할 때 로그인 및 로그아웃 이벤트를 받을 대리자를 설정합니다. 예를 들면 다음과 같습니다.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;
- 일반적으로 앱의
application:didFinishLaunchingWithOptions:
메서드에서FirebaseApp
공유 인스턴스를 구성합니다.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 Limited 로그인 구현
'기본' Facebook 로그인 대신 Facebook Limited 로그인을 사용하려면 다음 단계를 완료하세요.
- 개발자 문서를 참고해 앱에 Facebook Limited 로그인을 통합합니다.
- 로그인 요청마다 고유한 임의 문자열인 'nonce'가 생성되며 이 문자열은 앱의 인증 요청에 대한 응답으로 ID 토큰이 명시적으로 부여되었는지 확인하는 데 사용됩니다. 릴레이 공격을 방지하려면 이 단계가 필요합니다.
다음 예시에서와 같이 iOS에서
SecRandomCopyBytes(_:_:_)
를 사용하여 암호로 보호된 nonce를 생성할 수 있습니다.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
로 설정한 후 nonce를 연결합니다. 예를 들면 다음과 같습니다.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;
- 일반적으로 앱의
application:didFinishLaunchingWithOptions:
메서드에서FirebaseApp
공유 인스턴스를 구성합니다.Swift
// Use Firebase library to configure APIs FirebaseApp.configure()
Objective-C
// Use Firebase library to configure APIs [FIRApp configure];
- 사용자가 성공적으로 로그인하면
didCompleteWithResult:error:
구현에서 해시되지 않은 nonce가 포함된 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 사용자에 대해 자세히 알아봅니다.
- 다른 ID 공급업체로 사용자를 로그인 처리합니다.