Join us for Firebase Summit on November 10, 2021. Tune in to learn how Firebase can help you accelerate app development, release with confidence, and scale with ease. Register

Xác thực bằng Apple trên iOS

Bạn có thể cho phép người dùng của mình xác thực với Firebase bằng ID Apple của họ bằng cách sử dụng SDK Firebase để thực hiện quy trình đăng nhập OAuth 2.0 end-to-end.

Trước khi bắt đầu

Để đăng nhập người dùng bằng Apple, trước tiên hãy định cấu hình Đăng nhập với Apple trên trang web dành cho nhà phát triển của Apple, sau đó bật Apple làm nhà cung cấp dịch vụ đăng nhập cho dự án Firebase của bạn.

Tham gia chương trình nhà phát triển Apple

Đăng nhập với Apple chỉ có thể được cấu hình bởi các thành viên của Apple phát triển chương trình .

Định cấu hình Đăng nhập với Apple

  1. Kích hoạt tính năng Đăng nhập với Apple cho ứng dụng của bạn trên giấy chứng nhận, nhận dạng & Profiles trang của trang web nhà phát triển của Apple.
  2. Nếu bạn sử dụng bất kỳ tính năng căn cứ hỏa lực xác thực của email tới người dùng, bao gồm cả liên kết email đăng nhập, xác minh địa chỉ email, thay đổi tài khoản thu hồi, và những người khác, cấu hình các dịch vụ chuyển tiếp email cá nhân của Apple và đăng ký noreply@ YOUR_FIREBASE_PROJECT_ID .firebaseapp.com (hoặc miền mẫu email tùy chỉnh của bạn) để Apple có thể chuyển tiếp các email do Xác thực Firebase gửi đến các địa chỉ email Apple ẩn danh.

Cho phép Apple làm nhà cung cấp dịch vụ đăng nhập

  1. Thêm căn cứ hỏa lực cho dự án iOS của bạn . Đảm bảo đăng ký ID gói của ứng dụng khi bạn thiết lập ứng dụng của mình trong bảng điều khiển Firebase.
  2. Trong căn cứ hỏa lực console , hãy mở phần Auth. Trên Đăng nhập tab phương pháp, cho phép các nhà cung cấp của Apple. Nếu bạn chỉ sử dụng Đăng nhập bằng Apple trong ứng dụng iOS, bạn có thể để trống các trường ID dịch vụ, ID nhóm Apple, khóa cá nhân và ID khóa.

Tuân thủ các yêu cầu về dữ liệu ẩn danh của Apple

Đăng nhập với Apple cung cấp cho người dùng tùy chọn của nặc danh hóa dữ liệu của họ, bao gồm địa chỉ email của họ, khi đăng nhập. Người dùng chọn tùy chọn này có địa chỉ email với các tên miền privaterelay.appleid.com . Khi bạn sử dụng Đăng nhập bằng Apple trong ứng dụng của mình, bạn phải tuân thủ mọi chính sách hoặc điều khoản hiện hành dành cho nhà phát triển từ Apple về các ID Apple ẩn danh này.

Điều này bao gồm việc nhận được bất kỳ sự đồng ý cần thiết nào của người dùng trước khi bạn liên kết bất kỳ thông tin cá nhân nhận dạng trực tiếp nào với ID Apple ẩn danh. Khi sử dụng Xác thực Firebase, điều này có thể bao gồm các hành động sau:

  • Liên kết địa chỉ email với ID Apple ẩn danh hoặc ngược lại.
  • Liên kết số điện thoại với ID Apple ẩn danh hoặc ngược lại
  • Liên kết thông tin đăng nhập xã hội không ẩn danh (Facebook, Google, v.v.) với ID Apple ẩn danh hoặc ngược lại.

Danh sách trên không toàn diện. Tham khảo Thỏa thuận cấp phép chương trình nhà phát triển của Apple trong phần Tư cách thành viên của tài khoản nhà phát triển của bạn để đảm bảo ứng dụng của bạn đáp ứng các yêu cầu của Apple.

Đăng nhập với Apple và xác thực bằng Firebase

Để xác thực với một tài khoản Apple, đầu tiên đăng ký người sử dụng trong Apple của họ tài khoản sử dụng của Apple AuthenticationServices khuôn khổ , và sau đó sử dụng ID token từ phản ứng của Apple để tạo ra một căn cứ hỏa lực AuthCredential đối tượng:

  1. Đối với mọi yêu cầu đăng nhập, hãy tạo một chuỗi ngẫu nhiên — "nonce" —mà bạn sẽ sử dụng để đảm bảo mã thông báo ID bạn nhận được đã được cấp cụ thể để đáp ứng yêu cầu xác thực của ứng dụng của bạn. Bước này rất quan trọng để ngăn chặn các cuộc tấn công phát lại.

    Bạn có thể tạo ra một nonce mã hóa bảo mật trên iOS với SecRandomCopyBytes(_:_:_) , như trong ví dụ sau:

    Nhanh

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

    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];
    }
        

    Bạn sẽ gửi hàm băm SHA256 của nonce cùng với yêu cầu đăng nhập của mình, mà Apple sẽ chuyển không thay đổi trong phản hồi. Firebase xác thực phản hồi bằng cách băm nonce ban đầu và so sánh nó với giá trị mà Apple đã chuyển.

    Nhanh

    @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;
    }
        
  2. Bắt đầu quy trình đăng nhập của Apple, bao gồm trong yêu cầu của bạn hàm băm SHA256 của nonce và lớp đại biểu sẽ xử lý phản hồi của Apple (xem bước tiếp theo):

    Nhanh

    import CryptoKit
    
    // Unhashed nonce.
    fileprivate var currentNonce: String?
    
    @available(iOS 13, *)
    func startSignInWithAppleFlow() {
      let nonce = randomNonceString()
      currentNonce = nonce
      let appleIDProvider = ASAuthorizationAppleIDProvider()
      let request = appleIDProvider.createRequest()
      request.requestedScopes = [.fullName, .email]
      request.nonce = sha256(nonce)
    
      let authorizationController = ASAuthorizationController(authorizationRequests: [request])
      authorizationController.delegate = self
      authorizationController.presentationContextProvider = self
      authorizationController.performRequests()
    }
    

    Objective-C

    @import CommonCrypto;
    
    - (void)startSignInWithAppleFlow {
      NSString *nonce = [self randomNonce:32];
      self.currentNonce = nonce;
      ASAuthorizationAppleIDProvider *appleIDProvider = [[ASAuthorizationAppleIDProvider alloc] init];
      ASAuthorizationAppleIDRequest *request = [appleIDProvider createRequest];
      request.requestedScopes = @[ASAuthorizationScopeFullName, ASAuthorizationScopeEmail];
      request.nonce = [self stringBySha256HashingString:nonce];
    
      ASAuthorizationController *authorizationController =
          [[ASAuthorizationController alloc] initWithAuthorizationRequests:@[request]];
      authorizationController.delegate = self;
      authorizationController.presentationContextProvider = self;
      [authorizationController performRequests];
    }
    
  3. Xử lý phản ứng của Apple trong việc thực hiện lại ASAuthorizationControllerDelegate . Nếu đăng nhập thành công, hãy sử dụng mã thông báo ID từ phản hồi của Apple với nonce chưa băm để xác thực với Firebase:

    Nhanh

    @available(iOS 13.0, *)
    extension MainViewController: ASAuthorizationControllerDelegate {
    
      func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
        if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
          guard let nonce = currentNonce else {
            fatalError("Invalid state: A login callback was received, but no login request was sent.")
          }
          guard let appleIDToken = appleIDCredential.identityToken else {
            print("Unable to fetch identity token")
            return
          }
          guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
            print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
            return
          }
          // Initialize a Firebase credential.
          let credential = OAuthProvider.credential(withProviderID: "apple.com",
                                                    IDToken: idTokenString,
                                                    rawNonce: nonce)
          // Sign in with Firebase.
          Auth.auth().signIn(with: credential) { (authResult, error) in
            if error {
              // Error. If error.code == .MissingOrInvalidNonce, make sure
              // you're sending the SHA256-hashed nonce as a hex string with
              // your request to Apple.
              print(error.localizedDescription)
              return
            }
            // User is signed in to Firebase with Apple.
            // ...
          }
        }
      }
    
      func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
        // Handle error.
        print("Sign in with Apple errored: \(error)")
      }
    
    }
    

    Objective-C

    - (void)authorizationController:(ASAuthorizationController *)controller
       didCompleteWithAuthorization:(ASAuthorization *)authorization API_AVAILABLE(ios(13.0)) {
      if ([authorization.credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
        ASAuthorizationAppleIDCredential *appleIDCredential = authorization.credential;
        NSString *rawNonce = self.currentNonce;
        NSAssert(rawNonce != nil, @"Invalid state: A login callback was received, but no login request was sent.");
    
        if (appleIDCredential.identityToken == nil) {
          NSLog(@"Unable to fetch identity token.");
          return;
        }
    
        NSString *idToken = [[NSString alloc] initWithData:appleIDCredential.identityToken
                                                  encoding:NSUTF8StringEncoding];
        if (idToken == nil) {
          NSLog(@"Unable to serialize id token from data: %@", appleIDCredential.identityToken);
        }
    
        // Initialize a Firebase credential.
        FIROAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"apple.com"
                                                                            IDToken:idToken
                                                                           rawNonce:rawNonce];
    
        // Sign in with Firebase.
        [[FIRAuth auth] signInWithCredential:credential
                                  completion:^(FIRAuthDataResult * _Nullable authResult,
                                               NSError * _Nullable error) {
          if (error != nil) {
            // Error. If error.code == FIRAuthErrorCodeMissingOrInvalidNonce,
            // make sure you're sending the SHA256-hashed nonce as a hex string
            // with your request to Apple.
            return;
          }
          // Sign-in succeeded!
        }];
      }
    }
    
    - (void)authorizationController:(ASAuthorizationController *)controller
               didCompleteWithError:(NSError *)error API_AVAILABLE(ios(13.0)) {
      NSLog(@"Sign in with Apple errored: %@", error);
    }
    

Không giống như các nhà cung cấp khác được Firebase Auth hỗ trợ, Apple không cung cấp URL ảnh.

Ngoài ra, khi người dùng chọn không chia sẻ email của họ với các ứng dụng, các quy định của Apple một địa chỉ email duy nhất cho người dùng đó (có dạng xyz@privaterelay.appleid.com ), mà nó chia sẻ với ứng dụng của bạn. Nếu bạn đã định cấu hình dịch vụ chuyển tiếp email riêng tư, Apple sẽ chuyển tiếp các email được gửi đến địa chỉ ẩn danh đến địa chỉ email thực của người dùng.

Táo thông tin chỉ chia sẻ người dùng như tên hiển thị với các ứng dụng lần đầu tiên một người dùng đăng nhập. Thông thường, căn cứ hỏa lực các cửa hàng tên hiển thị lần đầu tiên một người dùng đăng nhập với Apple, mà bạn có thể nhận được với Auth.auth().currentUser.displayName . Tuy nhiên, nếu trước đây bạn đã sử dụng Apple để đăng nhập người dùng vào ứng dụng mà không sử dụng Firebase, thì Apple sẽ không cung cấp cho Firebase tên hiển thị của người dùng.

Xác thực lại và liên kết tài khoản

Các mô hình tương tự có thể được sử dụng với reauthenticateWithCredential() , mà bạn có thể sử dụng để lấy một chứng chỉ mới cho các hoạt động nhạy cảm đòi hỏi phải đăng nhập gần đây:

Nhanh

// Initialize a fresh Apple credential with Firebase.
let credential = OAuthProvider.credential(
  withProviderID: "apple.com",
  IDToken: appleIdToken,
  rawNonce: rawNonce
)
// Reauthenticate current Apple user with fresh Apple credential.
Auth.auth().currentUser.reauthenticate(with: credential) { (authResult, error) in
  guard error != nil else { return }
  // Apple user successfully re-authenticated.
  // ...
}

Objective-C

FIRAuthCredential *credential = [FIROAuthProvider credentialWithProviderID:@"apple.com",
                                                                   IDToken:appleIdToken,
                                                                  rawNonce:rawNonce];
[[FIRAuth auth].currentUser
    reauthenticateWithCredential:credential
                      completion:^(FIRAuthDataResult * _Nullable authResult,
                                   NSError * _Nullable error) {
  if (error) {
    // Handle error.
  }
  // Apple user successfully re-authenticated.
  // ...
}];

Và, bạn có thể sử dụng linkWithCredential() để liên kết các nhà cung cấp danh tính khác nhau vào các tài khoản hiện có.

Lưu ý rằng Apple yêu cầu bạn phải có sự đồng ý rõ ràng từ người dùng trước khi bạn liên kết tài khoản Apple của họ với dữ liệu khác.

Đăng nhập bằng Apple sẽ không cho phép bạn sử dụng lại thông tin xác thực để liên kết với tài khoản hiện có. Nếu bạn muốn liên kết Đăng nhập bằng thông tin đăng nhập Apple với một tài khoản khác, trước tiên bạn phải cố gắng liên kết các tài khoản bằng Đăng nhập cũ bằng thông tin đăng nhập Apple và sau đó kiểm tra lỗi được trả về để tìm thông tin đăng nhập mới. Thông tin đăng nhập mới sẽ được đặt tại của lỗi userInfo từ điển và có thể được truy cập thông qua FIRAuthErrorUserInfoUpdatedCredentialKey then chốt.

Ví dụ: để liên kết tài khoản Facebook với tài khoản Firebase hiện tại, hãy sử dụng mã thông báo truy cập bạn nhận được khi đăng nhập người dùng vào Facebook:

Nhanh

// Initialize a Facebook credential with Firebase.
let credential = FacebookAuthProvider.credential(
  withAccessToken: AccessToken.current!.tokenString
)
// Assuming the current user is an Apple user linking a Facebook provider.
Auth.auth().currentUser.link(with: credential) { (authResult, error) in
  // Facebook credential is linked to the current Apple user.
  // The user can now sign in with Facebook or Apple to the same Firebase
  // account.
  // ...
}

Objective-C

// Initialize a Facebook credential with Firebase.
FacebookAuthCredential *credential = [FIRFacebookAuthProvider credentialWithAccessToken:accessToken];
// Assuming the current user is an Apple user linking a Facebook provider.
[FIRAuth.auth linkWithCredential:credential completion:^(FIRAuthDataResult * _Nullable authResult, NSError * _Nullable error) {
  // Facebook credential is linked to the current Apple user.
  // The user can now sign in with Facebook or Apple to the same Firebase
  // account.
  // ...
}];

Bước tiếp theo

Sau khi người dùng đăng nhập lần đầu tiên, một tài khoản người dùng mới sẽ được tạo và liên kết với thông tin đăng nhập — nghĩa là tên người dùng và mật khẩu, số điện thoại hoặc thông tin nhà cung cấp xác thực — người dùng đã đăng nhập bằng. Tài khoản mới này được lưu trữ như một phần của dự án Firebase của bạn và có thể được sử dụng để xác định người dùng trên mọi ứng dụng trong dự án của bạn, bất kể người dùng đăng nhập bằng cách nào.

  • Trong các ứng dụng của bạn, bạn có thể nhận được thông tin hồ sơ cơ bản của người dùng từ các FIRUser đối tượng. Xem Quản lý người dùng .

  • Trong Cơ sở dữ liệu căn cứ hỏa lực Realtime của bạn và lưu trữ đám mây Security Rules , bạn có thể nhận được đã đăng nhập của người sử dụng ID người dùng duy nhất từ auth biến, và sử dụng nó để kiểm soát dữ liệu người dùng có thể truy cập.

Bạn có thể cho phép người dùng đăng nhập vào ứng dụng của bạn sử dụng nhiều nhà cung cấp chứng thực bởi liên kết auth thông tin cung cấp cho một tài khoản người dùng hiện có.

Đăng xuất khỏi một người sử dụng, hãy gọi signOut: .

Nhanh

    let firebaseAuth = Auth.auth()
do {
  try firebaseAuth.signOut()
} catch let signOutError as NSError {
  print("Error signing out: %@", signOutError)
}
  

Objective-C

    NSError *signOutError;
BOOL status = [[FIRAuth auth] signOut:&signOutError];
if (!status) {
  NSLog(@"Error signing out: %@", signOutError);
  return;
}

Bạn cũng có thể muốn thêm mã xử lý lỗi cho đầy đủ các lỗi xác thực. Xem lỗi Xử lý .