Firebase Authentication with Identity Platform으로 업그레이드했다면 iOS 앱에 SMS 다중 인증(MFA)을 추가할 수 있습니다.
다중 인증(MFA)을 통해 앱의 보안이 강화됩니다. 공격자는 종종 비밀번호와 소셜 미디어 계정을 유출시키지만 문자 메시지를 가로채는 것은 더 어렵습니다.
시작하기 전에
- 다중 인증(MFA)을 지원하는 제공업체를 하나 이상 사용 설정합니다. 전화 인증, 익명 인증, Apple Game Center를 제외한 모든 제공업체는 MFA를 지원합니다. 
- 앱이 사용자 이메일을 확인하고 있는지 확인합니다. MFA를 사용하려면 이메일 인증이 필요합니다. 이를 통해 악의적인 행위자가 자신이 소유하지 않은 이메일에 서비스를 등록한 후 두 번째 단계를 추가하여 실제 소유자의 접근을 막는 일을 방지할 수 있습니다. 
다중 인증(MFA) 사용 설정
- Firebase Console의 인증 > 로그인 방법 페이지를 엽니다. 
- 고급 섹션에서 SMS 다중 인증(MFA)을 사용 설정합니다. - 앱을 테스트할 전화번호도 입력해야 합니다. 선택사항이지만 개발 중 제한이 발생하지 않도록 테스트 전화번호를 등록하는 것이 좋습니다. 
- 아직 앱 도메인을 승인하지 않았다면 Firebase Console의 인증 > 설정 페이지에서 허용 목록에 추가합니다. 
앱 확인
Firebase는 SMS 요청이 앱에서 오는지 확인해야 합니다. 이 작업은 두 가지 방법으로 수행할 수 있습니다.
- 자동 APN 알림: 사용자를 처음으로 로그인 처리하면 Firebase에서 사용자의 기기에 자동 푸시 알림을 보낼 수 있습니다. 앱이 알림을 수신하면 인증을 진행할 수 있습니다. iOS 8.0부터는 사용자에게 푸시 알림을 허용하도록 요청하지 않아도 이 방법을 사용할 수 있습니다. 
- reCAPTCHA 인증: 사용자가 백그라운드 새로고침을 사용 중지하거나 iOS 시뮬레이터에서 앱을 테스트하는 경우와 같이 자동 알림을 보낼 수 없는 경우 reCAPTCHA를 사용할 수 있습니다. 대부분 reCAPTCHA는 사용자 상호작용 없이 자동으로 해결됩니다. 
자동 알림 사용
Firebase와 함께 사용하기 위해 APN 알림을 사용 설정하는 방법은 다음과 같습니다.
- Xcode에서 프로젝트에 푸시 알림을 사용 설정합니다. 
- Firebase Console을 사용하여 APN 인증 키를 업로드합니다. 변경사항이 자동으로 Google Cloud Firebase에 적용됩니다. 아직 APN 인증 키가 없으면 FCM에서 APN 구성을 참조하여 설정 방법을 알아보세요. - Firebase Console을 엽니다. 
- 프로젝트 설정으로 이동합니다. 
- 클라우드 메시징 탭을 선택합니다. 
- iOS 앱 구성 섹션의 APN 인증 키에서 업로드를 클릭하여 개발 인증 키, 프로덕션 인증 키 또는 둘 다를 업로드합니다. 최소 하나 이상이 필요합니다. 
- 키를 선택합니다. 
- 키의 키 ID를 추가합니다. 키 ID는 Apple 개발자 구성원 센터의 인증서, 식별자, 프로필에서 찾을 수 있습니다. 
- 업로드를 클릭합니다. 
 
APN 인증서가 이미 있다면 인증서를 대신 업로드할 수 있습니다.
reCAPTCHA 인증 사용
클라이언트 SDK에서 reCAPTCHA를 사용하도록 설정하려면 다음 단계를 따르세요.
- Xcode에서 프로젝트 구성을 엽니다. 
- 왼쪽 트리 보기에서 프로젝트 이름을 더블클릭합니다. 
- 타겟 섹션에서 앱을 선택합니다. 
- 정보 탭을 선택합니다. 
- URL 유형 섹션을 펼칩니다. 
- 화면의 + 버튼을 클릭합니다. 
- URL 스키마 입력란에 반전된 클라이언트 ID를 입력합니다. 이 값은 - GoogleService-Info.plist구성 파일에- REVERSED_CLIENT_ID로 표시됩니다.
완료되면 구성이 다음과 비슷하게 표시됩니다.

선택적으로 reCAPTCHA를 표시할 때 앱에서 SFSafariViewController 또는 UIWebView를 표시하는 방식을 맞춤설정할 수 있습니다. 이렇게 하려면 FIRAuthUIDelegate 프로토콜을 준수하는 커스텀 클래스를 만들어 verifyPhoneNumber:UIDelegate:completion:에 전달합니다.
등록 패턴 선택
앱에 다중 인증(MFA)이 필요한지 여부 및 사용자 등록 방법과 시기를 선택할 수 있습니다. 일반적인 패턴은 다음과 같습니다.
- 등록 시 사용자의 두 번째 단계를 등록합니다. 앱이 모든 사용자에게 다중 인증(MFA)을 요구한다면 이 방법을 사용하세요. 두 번째 단계를 등록하려면 계정에 인증된 이메일 주소가 있어야 하므로 등록 과정에서 이를 수용해야 합니다. 
- 등록 시 건너뛸 수 있는 옵션으로 두 번째 단계를 등록하는 옵션을 제공하세요. 다중 인증(MFA)을 권고하지만 필수이지는 않은 앱은 이 방법을 사용하는 것이 좋습니다. 
- 가입 화면이 아닌 사용자의 계정 또는 프로필 관리 페이지에서 두 번째 단계를 추가할 수 있도록 합니다. 이렇게 하면 등록 프로세스 중에 발생하는 마찰을 최소화하면서도 보안에 민감한 사용자에게 다중 인증(MFA)을 제공할 수 있습니다. 
- 사용자가 보안 요구사항이 향상된 기능에 액세스하려고 할 때 두 번째 단계를 점진적으로 추가하도록 합니다. 
두 번째 단계 등록
사용자의 새로운 두 번째 단계를 등록하려면 다음 단계를 따르세요.
- 사용자를 다시 인증합니다. 
- 사용자에게 전화번호를 입력하도록 요청합니다. 
- 사용자를 위한 다중 세션을 가져옵니다. - Swift- authResult.user.multiFactor.getSessionWithCompletion() { (session, error) in // ... }- Objective-C- [authResult.user.multiFactor getSessionWithCompletion:^(FIRMultiFactorSession * _Nullable session, NSError * _Nullable error) { // ... }];
- 사용자 전화로 인증 코드를 보내세요. 전화번호 형식은 앞에 - +를 입력하고 구두점이나 공백은 사용하지 마세요(예:- +15105551234).- Swift- // Send SMS verification code. PhoneAuthProvider.provider().verifyPhoneNumber( phoneNumber, uiDelegate: nil, multiFactorSession: session) { (verificationId, error) in // verificationId will be needed for enrollment completion. }- Objective-C- // Send SMS verification code. [FIRPhoneAuthProvider.provider verifyPhoneNumber:phoneNumber UIDelegate:nil multiFactorSession:session completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) { // verificationId will be needed for enrollment completion. }];- 필수 사항은 아니지만, 사용자에게 SMS 메시지가 전송되고 표준 요금이 적용된다는 점을 미리 알리는 것이 좋습니다. - verifyPhoneNumber()메서드는 자동 푸시 알림을 사용하여 백그라운드에서 앱 인증 프로세스를 시작합니다. 자동 푸시 알림을 사용할 수 없는 경우 reCAPTCHA 테스트가 대신 실행됩니다.
- SMS 코드가 전송되면 사용자에게 코드를 확인하도록 요청합니다. 그런 다음 응답을 사용하여 - PhoneAuthCredential을 빌드합니다.- Swift- // Ask user for the verification code. Then: let credential = PhoneAuthProvider.provider().credential( withVerificationID: verificationId, verificationCode: verificationCode)- Objective-C- // Ask user for the SMS verification code. Then: FIRPhoneAuthCredential *credential = [FIRPhoneAuthProvider.provider credentialWithVerificationID:verificationID verificationCode:kPhoneSecondFactorVerificationCode];
- 어설션 객체를 초기화합니다. - Swift- let assertion = PhoneMultiFactorGenerator.assertion(with: credential)- Objective-C- FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
- 등록을 완료합니다. 선택사항으로 두 번째 단계의 표시 이름을 지정할 수 있습니다. 인증 과정에서 전화번호가 마스킹되므로(예: +1******1234) 두 번째 단계가 여러 개인 사용자에게 유용합니다. - Swift- // Complete enrollment. This will update the underlying tokens // and trigger ID token change listener. user.multiFactor.enroll(with: assertion, displayName: displayName) { (error) in // ... }- Objective-C- // Complete enrollment. This will update the underlying tokens // and trigger ID token change listener. [authResult.user.multiFactor enrollWithAssertion:assertion displayName:nil completion:^(NSError * _Nullable error) { // ... }];
다음 코드는 두 번째 단계를 등록하는 전체 예시를 보여줍니다.
Swift
let user = Auth.auth().currentUser
user?.multiFactor.getSessionWithCompletion({ (session, error) in
  // Send SMS verification code.
  PhoneAuthProvider.provider().verifyPhoneNumber(
    phoneNumber,
    uiDelegate: nil,
    multiFactorSession: session
  ) { (verificationId, error) in
    // verificationId will be needed for enrollment completion.
    // Ask user for the verification code.
    let credential = PhoneAuthProvider.provider().credential(
      withVerificationID: verificationId!,
      verificationCode: phoneSecondFactorVerificationCode)
    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    user?.multiFactor.enroll(with: assertion, displayName: displayName) { (error) in
      // ...
    }
  }
})
Objective-C
FIRUser *user = FIRAuth.auth.currentUser;
[user.multiFactor getSessionWithCompletion:^(FIRMultiFactorSession * _Nullable session,
                                              NSError * _Nullable error) {
    // Send SMS verification code.
    [FIRPhoneAuthProvider.provider
      verifyPhoneNumber:phoneNumber
      UIDelegate:nil
      multiFactorSession:session
      completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) {
        // verificationId will be needed for enrollment completion.
        // Ask user for the verification code.
        // ...
        // Then:
        FIRPhoneAuthCredential *credential =
            [FIRPhoneAuthProvider.provider credentialWithVerificationID:verificationID
                                                        verificationCode:kPhoneSecondFactorVerificationCode];
        FIRMultiFactorAssertion *assertion =
            [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
        // Complete enrollment. This will update the underlying tokens
        // and trigger ID token change listener.
        [user.multiFactor enrollWithAssertion:assertion
                                  displayName:displayName
                                    completion:^(NSError * _Nullable error) {
            // ...
        }];
    }];
}];
수고하셨습니다. 사용자의 두 번째 인증 단계가 성공적으로 등록되었습니다.
두 번째 단계 인증으로 사용자 로그인 처리
2단계 SMS 인증으로 사용자를 로그인 처리하려면 다음 안내를 따르세요.
- 첫 번째 단계로 사용자를 로그인 처리한 후 다중 인증(MFA)이 필요함을 나타내는 오류를 확인합니다. 이 오류에는 리졸버, 등록된 두 번째 단계에 대한 힌트, 사용자가 첫 번째 단계에서 성공적으로 인증되었음을 증명하는 기본 세션이 포함됩니다. - 예를 들어 사용자의 첫 번째 단계가 이메일과 비밀번호인 경우는 다음과 같습니다. - Swift- Auth.auth().signIn( withEmail: email, password: password ) { (result, error) in let authError = error as NSError if authError?.code == AuthErrorCode.secondFactorRequired.rawValue { // The user is a multi-factor user. Second factor challenge is required. let resolver = authError!.userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver // ... } else { // Handle other errors such as wrong password. } }- Objective-C- [FIRAuth.auth signInWithEmail:email password:password completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { if (error == nil || error.code != FIRAuthErrorCodeSecondFactorRequired) { // User is not enrolled with a second factor and is successfully signed in. // ... } else { // The user is a multi-factor user. Second factor challenge is required. } }];- 사용자의 첫 번째 단계가 OAuth와 같은 제휴 공급업체인 경우 - getCredentialWith()호출 후 오류를 포착합니다.
- 사용자가 보조 단계를 여러 개 등록한 경우 어떤 단계를 사용할지 질문합니다. - resolver.hints[selectedIndex].phoneNumber를 사용하여 마스킹된 전화번호를 가져오고- resolver.hints[selectedIndex].displayName을 사용하여 표시 이름을 가져올 수 있습니다.- Swift- // Ask user which second factor to use. Then: if resolver.hints[selectedIndex].factorID == PhoneMultiFactorID { // User selected a phone second factor. // ... } else if resolver.hints[selectedIndex].factorID == TotpMultiFactorID { // User selected a TOTP second factor. // ... } else { // Unsupported second factor. }- Objective-C- FIRMultiFactorResolver *resolver = (FIRMultiFactorResolver *) error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey]; // Ask user which second factor to use. Then: FIRPhoneMultiFactorInfo *hint = (FIRPhoneMultiFactorInfo *) resolver.hints[selectedIndex]; if (hint.factorID == FIRPhoneMultiFactorID) { // User selected a phone second factor. // ... } else if (hint.factorID == FIRTOTPMultiFactorID) { // User selected a TOTP second factor. // ... } else { // Unsupported second factor. }
- 사용자 전화로 인증 코드 보내기 - Swift- // Send SMS verification code. let hint = resolver.hints[selectedIndex] as! PhoneMultiFactorInfo PhoneAuthProvider.provider().verifyPhoneNumber( with: hint, uiDelegate: nil, multiFactorSession: resolver.session ) { (verificationId, error) in // verificationId will be needed for sign-in completion. }- Objective-C- // Send SMS verification code [FIRPhoneAuthProvider.provider verifyPhoneNumberWithMultiFactorInfo:hint UIDelegate:nil multiFactorSession:resolver.session completion:^(NSString * _Nullable verificationID, NSError * _Nullable error) { if (error != nil) { // Failed to verify phone number. } }];
- SMS 코드가 전송되면 사용자에게 코드를 확인하고 이를 사용하여 - PhoneAuthCredential을 빌드하도록 요청합니다.- Swift- // Ask user for the verification code. Then: let credential = PhoneAuthProvider.provider().credential( withVerificationID: verificationId!, verificationCode: verificationCodeFromUser)- Objective-C- // Ask user for the SMS verification code. Then: FIRPhoneAuthCredential *credential = [FIRPhoneAuthProvider.provider credentialWithVerificationID:verificationID verificationCode:verificationCodeFromUser];
- 사용자 인증 정보로 어설션 객체를 초기화합니다. - Swift- let assertion = PhoneMultiFactorGenerator.assertion(with: credential)- Objective-C- FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
- 로그인 문제를 해결합니다. 그런 다음 표준 공급업체별 데이터 및 인증 사용자 인증 정보가 포함된 원래 로그인 결과에 액세스할 수 있습니다. - Swift- // Complete sign-in. This will also trigger the Auth state listeners. resolver.resolveSignIn(with: assertion) { (authResult, error) in // authResult will also contain the user, additionalUserInfo, optional // credential (null for email/password) associated with the first factor sign-in. // For example, if the user signed in with Google as a first factor, // authResult.additionalUserInfo will contain data related to Google provider that // the user signed in with. // user.credential contains the Google OAuth credential. // user.credential.accessToken contains the Google OAuth access token. // user.credential.idToken contains the Google OAuth ID token. }- Objective-C- // Complete sign-in. [resolver resolveSignInWithAssertion:assertion completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) { if (error != nil) { // User successfully signed in with the second factor phone number. } }];
아래 코드는 다중 인증 사용자를 위한 전체 로그인 예시를 보여줍니다.
Swift
Auth.auth().signIn(
  withEmail: email,
  password: password
) { (result, error) in
  let authError = error as NSError?
  if authError?.code == AuthErrorCode.secondFactorRequired.rawValue {
    let resolver =
      authError!.userInfo[AuthErrorUserInfoMultiFactorResolverKey] as! MultiFactorResolver
    // Ask user which second factor to use.
    // ...
    // Then:
    let hint = resolver.hints[selectedIndex] as! PhoneMultiFactorInfo
    // Send SMS verification code
    PhoneAuthProvider.provider().verifyPhoneNumber(
      with: hint,
      uiDelegate: nil,
      multiFactorSession: resolver.session
    ) { (verificationId, error) in
      if error != nil {
        // Failed to verify phone number.
      }
      // Ask user for the SMS verification code.
      // ...
      // Then:
      let credential = PhoneAuthProvider.provider().credential(
        withVerificationID: verificationId!,
        verificationCode: verificationCodeFromUser)
      let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
      // Complete sign-in.
      resolver.resolveSignIn(with: assertion) { (authResult, error) in
        if error != nil {
          // User successfully signed in with the second factor phone number.
        }
      }
    }
  }
}
Objective-C
[FIRAuth.auth signInWithEmail:email
                     password:password
                   completion:^(FIRAuthDataResult * _Nullable authResult,
                               NSError * _Nullable error) {
    if (error == nil || error.code != FIRAuthErrorCodeSecondFactorRequired) {
        // User is not enrolled with a second factor and is successfully signed in.
        // ...
    } else {
        FIRMultiFactorResolver *resolver =
            (FIRMultiFactorResolver *) error.userInfo[FIRAuthErrorUserInfoMultiFactorResolverKey];
        // Ask user which second factor to use.
        // ...
        // Then:
        FIRPhoneMultiFactorInfo *hint = (FIRPhoneMultiFactorInfo *) resolver.hints[selectedIndex];
        // Send SMS verification code
        [FIRPhoneAuthProvider.provider
          verifyPhoneNumberWithMultiFactorInfo:hint
                                    UIDelegate:nil
                            multiFactorSession:resolver.session
                                    completion:^(NSString * _Nullable verificationID,
                                                NSError * _Nullable error) {
            if (error != nil) {
                // Failed to verify phone number.
            }
            // Ask user for the SMS verification code.
            // ...
            // Then:
            FIRPhoneAuthCredential *credential =
                [FIRPhoneAuthProvider.provider
                  credentialWithVerificationID:verificationID
                              verificationCode:kPhoneSecondFactorVerificationCode];
            FIRMultiFactorAssertion *assertion =
                [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
            // Complete sign-in.
            [resolver resolveSignInWithAssertion:assertion
                                      completion:^(FIRAuthDataResult * _Nullable authResult,
                                                    NSError * _Nullable error) {
                if (error != nil) {
                    // User successfully signed in with the second factor phone number.
                }
            }];
        }];
    }
}];
수고하셨습니다. 다중 인증(MFA)을 사용하여 사용자가 성공적으로 로그인했습니다.
다음 단계
- Admin SDK를 사용하여 프로그래매틱 방식으로 다중 인증 사용자를 관리합니다.