Firebase Summit에서 발표된 모든 내용을 살펴보고 Firebase로 앱을 빠르게 개발하고 안심하고 앱을 실행하는 방법을 알아보세요. 자세히 알아보기

iOS 앱에 다단계 인증 추가

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

Identity Platform을 사용한 Firebase 인증으로 업그레이드한 경우 iOS 앱에 SMS 다단계 인증을 추가할 수 있습니다.

다단계 인증은 앱의 보안을 강화합니다. 공격자는 종종 암호와 소셜 계정을 손상시키지만 문자 메시지를 가로채는 것은 더 어렵습니다.

시작하기 전에

  1. 다단계 인증을 지원하는 하나 이상의 공급자를 활성화합니다. 전화 인증, 익명 인증 및 Apple Game Center를 제외한 모든 제공업체는 MFA를 지원합니다.

  2. 앱이 사용자 이메일을 확인하고 있는지 확인하십시오. MFA는 이메일 확인이 필요합니다. 이렇게 하면 악의적인 행위자가 자신이 소유하지 않은 이메일로 서비스에 등록한 다음 두 번째 요소를 추가하여 실제 소유자를 잠그는 것을 방지할 수 있습니다.

다단계 인증 활성화

  1. Firebase 콘솔의 인증 > 로그인 방법 페이지를 엽니다.

  2. 고급 섹션에서 SMS 다단계 인증 을 활성화합니다.

    앱을 테스트할 전화번호도 입력해야 합니다. 선택 사항이지만 개발 중 스로틀링을 방지하려면 테스트 전화 번호를 등록하는 것이 좋습니다.

  3. 앱의 도메인을 아직 승인하지 않은 경우 Firebase 콘솔의 인증 > 설정 페이지에서 허용 목록에 추가합니다.

앱 확인

Firebase는 SMS 요청이 앱에서 오는지 확인해야 합니다. 다음 두 가지 방법으로 이 작업을 수행할 수 있습니다.

  • 무음 APN 알림 : 사용자가 처음 로그인하면 Firebase에서 사용자의 기기로 무음 푸시 알림을 보낼 수 있습니다. 앱에서 알림을 받으면 인증을 진행할 수 있습니다. iOS 8.0부터는 푸시 알림이 이 방법을 사용하도록 허용하도록 사용자에게 요청할 필요가 없습니다.

  • reCAPTCHA 확인 : 자동 알림을 보낼 수 없는 경우(예: 사용자가 백그라운드 새로 고침을 비활성화했거나 iOS 시뮬레이터에서 앱을 테스트 중이기 때문에) reCAPTCHA를 사용할 수 있습니다. 많은 경우 reCAPTCHA는 사용자 상호 작용 없이 자동으로 해결됩니다.

무음 알림 사용

Firebase에서 사용할 APN 알림을 사용 설정하려면 다음 안내를 따르세요.

  1. Xcode에서 프로젝트에 대한 푸시 알림을 활성화 합니다.

  2. Firebase 콘솔을 사용하여 APN 인증 키를 업로드합니다(변경 사항은 자동으로 Google Cloud Firebase로 전달됨). APN 인증 키가 아직 없는 경우 FCM으로 APN 구성 을 참조하여 획득 방법을 알아보세요.

    1. Firebase 콘솔 을 엽니다.

    2. 프로젝트 설정 으로 이동합니다.

    3. 클라우드 메시징 탭을 선택합니다.

    4. APNs 인증 키 아래의 iOS 앱 구성 섹션에서 업로드 를 클릭합니다.

    5. 키를 선택합니다.

    6. 키의 키 ID를 추가합니다. Apple Developer Member Center인증서, 식별자 및 프로필 에서 키 ID를 찾을 수 있습니다.

    7. 업로드 를 클릭합니다.

APN 인증서가 이미 있는 경우 대신 인증서를 업로드할 수 있습니다.

reCAPTCHA 확인 사용

클라이언트 SDK에서 reCAPTCHA를 사용하도록 설정하려면 다음 안내를 따르세요.

  1. Xcode에서 프로젝트 구성을 엽니다.

  2. 왼쪽 트리 보기에서 프로젝트 이름을 두 번 클릭합니다.

  3. 대상 섹션에서 앱을 선택합니다.

  4. 정보 탭을 선택합니다.

  5. URL 유형 섹션을 펼치십시오.

  6. + 버튼을 클릭합니다.

  7. URL 스키마 필드에 역방향 클라이언트 ID를 입력합니다. GoogleService-Info.plist 구성 파일에 REVERSED_CLIENT_ID 로 나열된 이 값을 찾을 수 있습니다.

완료되면 구성이 다음과 유사해야 합니다.

맞춤 구성표

선택적으로 reCAPTCHA를 표시할 때 앱이 SFSafariViewController 또는 UIWebView 를 표시하는 방식을 사용자 지정할 수 있습니다. 이렇게 하려면 FIRAuthUIDelegate 프로토콜을 준수하는 사용자 지정 클래스를 만들고 이를 verifyPhoneNumber:UIDelegate:completion: 에 전달합니다.

등록 패턴 선택

앱에 다단계 인증이 필요한지 여부와 사용자를 등록하는 방법과 시기를 선택할 수 있습니다. 몇 가지 일반적인 패턴은 다음과 같습니다.

  • 등록의 일부로 사용자의 두 번째 요소를 등록합니다. 앱이 모든 사용자에 대해 다단계 인증을 요구하는 경우 이 방법을 사용하십시오. 두 번째 요소를 등록하려면 계정에 확인된 이메일 주소가 있어야 하므로 등록 과정에서 이를 수용해야 합니다.

  • 등록하는 동안 두 번째 요소를 등록하기 위해 건너뛸 수 있는 옵션을 제공합니다. 다단계 인증을 권장하지만 요구하지 않는 앱은 이 접근 방식을 선호할 수 있습니다.

  • 가입 화면 대신 사용자 계정 또는 프로필 관리 페이지에서 두 번째 요소를 추가하는 기능을 제공합니다. 이렇게 하면 등록 프로세스 중 마찰을 최소화하면서 보안에 민감한 사용자가 다중 요소 인증을 계속 사용할 수 있습니다.

  • 사용자가 보안 요구 사항이 강화된 기능에 액세스하려는 경우 두 번째 요소를 점진적으로 추가해야 합니다.

두 번째 요소 등록

사용자에 대한 새 2차 요소를 등록하려면:

  1. 사용자를 다시 인증합니다.

  2. 사용자에게 전화번호를 입력하도록 요청합니다.

  3. 사용자를 위한 다단계 세션 가져오기:

    빠른

    authResult.user.multiFactor.getSessionWithCompletion() { (session, error) in
      // ...
    }
    

    오브젝티브-C

    [authResult.user.multiFactor
      getSessionWithCompletion:^(FIRMultiFactorSession * _Nullable session,
                                NSError * _Nullable error) {
        // ...
    }];
    
  4. 사용자의 전화로 확인 메시지를 보냅니다. 전화번호 형식이 + 로 시작하고 다른 구두점이나 공백이 없는지 확인합니다(예: +15105551234 ).

    빠른

    // Send SMS verification code.
    PhoneAuthProvider.provider().verifyPhoneNumber(
      phoneNumber,
      uiDelegate: nil,
      multiFactorSession: session) { (verificationId, error) in
        // verificationId will be needed for enrollment completion.
    }
    

    오브젝티브-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 챌린지가 발행됩니다.

  5. SMS 코드가 전송되면 사용자에게 코드를 확인하도록 요청합니다. 그런 다음 응답을 사용하여 PhoneAuthCredential 을 빌드합니다.

    빠른

    // Ask user for the verification code. Then:
    let credential = PhoneAuthProvider.provider().credential(
      withVerificationID: verificationId,
      verificationCode: verificationCode)
    

    오브젝티브-C

    // Ask user for the SMS verification code. Then:
    FIRPhoneAuthCredential *credential = [FIRPhoneAuthProvider.provider
                                           credentialWithVerificationID:verificationID
                                           verificationCode:kPhoneSecondFactorVerificationCode];
    
  6. 어설션 개체를 초기화합니다.

    빠른

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

    오브젝티브-C

    FIRMultiFactorAssertion *assertion = [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
    
  7. 등록을 완료합니다. 선택적으로 두 번째 요소의 표시 이름을 지정할 수 있습니다. 인증 과정에서 전화번호가 마스킹되기 때문에(예: +1******1234) 여러 초 요소가 있는 사용자에게 유용합니다.

    빠른

    // Complete enrollment. This will update the underlying tokens
    // and trigger ID token change listener.
    user.multiFactor.enroll(with: assertion, displayName: displayName) { (error) in
      // ...
    }
    

    오브젝티브-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) {
        // ...
    }];
    

아래 코드는 두 번째 요소 등록의 전체 예를 보여줍니다.

빠른

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
      // ...
    }
  }
})

오브젝티브-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 인증으로 사용자를 로그인하려면:

  1. 첫 번째 요소로 사용자를 로그인한 다음 다단계 인증이 필요하다는 오류를 포착합니다. 이 오류에는 확인자, 등록된 두 번째 요소에 대한 힌트 및 사용자가 첫 번째 요소로 성공적으로 인증되었음을 증명하는 기본 세션이 포함됩니다.

    예를 들어 사용자의 첫 번째 요소가 이메일과 비밀번호인 경우:

    빠른

    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.
      }
    }
    

    오브젝티브-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() 호출 후 오류를 포착합니다.

  2. 사용자가 여러 개의 보조 요소를 등록한 경우 어느 것을 사용할지 물어봅니다. 마스킹된 전화번호는 resolver.hints[selectedIndex].phoneNumber 로, 표시 이름은 resolver.hints[selectedIndex].displayName 으로 얻을 수 있습니다.

    빠른

    // Ask user which second factor to use. Then:
    if resolver.hints[selectedIndex].factorID == PhoneMultiFactorID {
      // User selected a phone second factor.
      // ...
    } else {
      // Unsupported second factor.
      // Note that only phone second factors are currently supported.
    }
    

    오브젝티브-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 {
      // Unsupported second factor.
      // Note that only phone second factors are currently supported.
    }
    
  3. 사용자의 전화로 확인 메시지 보내기:

    빠른

    // 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.
    }
    

    오브젝티브-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.
        }
    }];
    
  4. SMS 코드가 전송되면 사용자에게 코드를 확인하고 PhoneAuthCredential 을 빌드하는 데 사용하도록 요청합니다.

    빠른

    // Ask user for the verification code. Then:
    let credential = PhoneAuthProvider.provider().credential(
      withVerificationID: verificationId!,
      verificationCode: verificationCodeFromUser)
    

    오브젝티브-C

    // Ask user for the SMS verification code. Then:
    FIRPhoneAuthCredential *credential =
        [FIRPhoneAuthProvider.provider
          credentialWithVerificationID:verificationID
                      verificationCode:verificationCodeFromUser];
    
  5. 자격 증명을 사용하여 어설션 개체를 초기화합니다.

    빠른

    let assertion = PhoneMultiFactorGenerator.assertion(with: credential)
    

    오브젝티브-C

    FIRMultiFactorAssertion *assertion =
        [FIRPhoneMultiFactorGenerator assertionWithCredential:credential];
    
  6. 로그인을 해결합니다. 그런 다음 표준 공급자별 데이터 및 인증 자격 증명을 포함하는 원래 로그인 결과에 액세스할 수 있습니다.

    빠른

    // 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.
    }
    

    오브젝티브-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.
        }
    }];
    

아래 코드는 다단계 사용자 로그인의 전체 예를 보여줍니다.

빠른

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.
        }
      }
    }
  }
}

오브젝티브-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.
                }
            }];
        }];
    }
}];

축하합니다! 다단계 인증을 사용하여 사용자를 성공적으로 로그인했습니다.

무엇 향후 계획

  • Admin SDK를 사용하여 프로그래밍 방식으로 다단계 사용자 를 관리합니다.