Xác thực bằng Apple

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

Trước khi bắt đầu

Để đăng nhập cho người dùng bằng Apple, trước tiên hãy định cấu hình tính năng Đăng nhập bằng 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.

Tham gia Chương trình nhà phát triển của Apple

Chỉ thành viên của Nhà phát triển Apple mới có thể định cấu hình cho tính năng Đăng nhập bằng Apple Chương trình.

Định cấu hình tính năng Đăng nhập bằng Apple

  1. Bật tính năng Đăng nhập bằng Apple cho ứng dụng của bạn trên Chứng chỉ, giá trị nhận dạng và Trang doanh nghiệp trang web dành cho nhà phát triển của Apple.
  2. Liên kết trang web với ứng dụng của bạn như mô tả trong phần đầu tiên trong phần Định cấu hình đăng nhập bằng Apple cho web. Khi được nhắc, hãy đăng ký URL sau đây làm URL trả lại:
    https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
    Bạn có thể lấy mã dự án Firebase trên trang cài đặt bảng điều khiển của Firebase. Khi bạn hoàn tất, hãy ghi lại Mã dịch vụ mới để có trong phần tiếp theo.
  3. Tạo một Đăng nhập bằng khoá riêng tư của Apple. Bạn sẽ cần khoá riêng tư và khoá mới mã nhận dạng trong phần tiếp theo.
  4. Nếu bạn sử dụng bất kỳ tính năng nào của Xác thực Firebase gửi email cho người dùng, bao gồm đăng nhập bằng đường liên kết email, xác minh địa chỉ email, thay đổi tài khoản thu hồi và những người khác, hãy định cấu hình dịch vụ chuyển tiếp email riêng tư của Apple và đăng ký noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (hoặc miền mẫu email tuỳ chỉnh của bạn) để Apple có thể chuyển tiếp các email được gửi bằng tính năng Xác thực Firebase sang đị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 Firebase vào dự án Apple của bạn. Hãy nhớ hãy đăng ký mã nhận dạng gói của ứng dụng khi thiết lập ứng dụng trong bảng điều khiển của Firebase.
  2. Trong bảng điều khiển của Firebase, hãy mở phần Xác thực. Trên thẻ Phương thức đăng nhập, bật nhà cung cấp Apple. Chỉ định Mã dịch vụ mà bạn đã tạo ở phần trước. Ngoài ra, trong Phần cấu hình luồng mã OAuth, chỉ định ID nhóm Apple của bạn và khoá cá nhân và mã nhận dạng khoá mà bạn đã tạo ở phần trước.

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

Tính năng Đăng nhập bằng Apple cho phép người dùng ẩn danh dữ liệu của họ, bao gồm cả địa chỉ email của họ khi đăng nhập. Người dùng chọn phương án này có địa chỉ email với miền privaterelay.appleid.com. Thời gian bạn sử dụng tính nă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 dành cho nhà phát triển hoặc điều khoản của Apple liên quan đến Mã nhận dạng.

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

  • Liên kết một địa chỉ email với một 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 một Apple ID ẩn danh hoặc ngược lại.

Danh sách bên trên chưa đầy đủ. Tham khảo Chương trình dành cho nhà phát triển của Apple Thoả thuận cấp phép trong phần Thành viên của tài khoản nhà phát triển để thực hiệ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 bằng Apple và xác thực bằng Firebase

Để xác thực bằng tài khoản Apple, trước tiên, hãy đăng nhập người dùng vào tài khoản Apple của họ bằng cách sử dụng khung AuthenticationServices của Apple, rồi sử dụng mã thông báo nhận dạng từ phản hồi của Apple để tạo Firebase Đối tượng AuthCredential:

  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 — một "nonce" – bạn sẽ sử dụng phương thức này để đảm bảo mã thông báo nhận dạng mà bạn nhận được là được cấp riêng theo yêu cầu xác thực của ứng dụng. Chiến dịch này là 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 một số chỉ dùng một lần được mã hoá bảo mật bằng SecRandomCopyBytes(_:_:_), như trong ví dụ sau:

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

    Bạn sẽ gửi hàm băm SHA256 của số chỉ dùng một lần cùng với yêu cầu đăng nhập. Apple sẽ truyề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 số chỉ dùng một lần ban đầu rồi so sánh với giá trị mà Apple chuyển qua.

    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;
    }
        
  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 số chỉ dùng một lần và lớp uỷ quyền sẽ xử lý phản hồi của Apple (xem bước tiếp theo):

    Swift

    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 hồi của Apple trong quá trình triển khai ASAuthorizationControllerDelegate. Nếu đăng nhập thành công, hãy sử dụng mã nhận dạng mã thông báo từ phản hồi của Apple với số chỉ dùng một lần chưa được băm để xác thực với Firebase:

    Swift

    @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, including the user's full name.
          let credential = OAuthProvider.appleCredential(withIDToken: idTokenString,
                                                            rawNonce: nonce,
                                                            fullName: appleIDCredential.fullName)
          // 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, including the user's full name.
        FIROAuthCredential *credential = [FIROAuthProvider appleCredentialWithIDToken:IDToken
                                                                             rawNonce:self.appleRawNonce
                                                                             fullName:appleIDCredential.fullName];
    
        // 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 tính năng Xác thực Firebase 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 ứng dụng, Apple cấp một địa chỉ email duy nhất cho người dùng đó (theo biểu mẫu xyz@privaterelay.appleid.com) mà hệ thống này 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 chuyển tiếp các email được gửi đến địa chỉ ẩn danh sang địa chỉ email thực của người dùng.

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

Bạn có thể dùng cùng một mẫu với reauthenticateWithCredential() có thể dùng để truy xuất thông tin đăng nhập mới cho những hoạt động nhạy cảm đòi hỏi lần đăng nhập gần đây:

Swift

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

Ngoài ra, bạn có thể sử dụng linkWithCredential() để liên kết nhiều nhà cung cấp danh tính với tài khoản hiện có.

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

Tính năng Đă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 một tài khoản hiện có. Nếu bạn muốn liên kết thông tin đăng nhập "Đăng nhập bằng Apple" với một thông tin đăng nhập khác tài khoản, trước tiên bạn phải cố gắng liên kết các tài khoản bằng cách sử dụng tính năng Đăng nhập cũ Thông tin đăng nhập Apple rồi 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ẽ nằm trong từ điển userInfo của lỗi và có thể có thể truy cập qua khoá AuthErrorUserInfoUpdatedCredentialKey.

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ã truy cập mà bạn nhận được từ việc đăng nhập người dùng vào Facebook:

Swift

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

Thu hồi mã thông báo

Apple yêu cầu các ứng dụng hỗ trợ tạo tài khoản phải cho phép người dùng bắt đầu xoá tài khoản của họ trong ứng dụng, như mô tả trong phần Bài đánh giá của cửa hàng ứng dụng Nguyên tắc

Để đáp ứng yêu cầu này, hãy triển khai các bước sau:

  1. Hãy đảm bảo bạn đã điền vào Mã dịch vụCấu hình quy trình mã OAuth của phần cấu hình của nhà cung cấp tính năng Đăng nhập bằng Apple, như được nêu trong Định cấu hình phần Đăng nhập bằng Apple.

  2. Vì Firebase không lưu trữ mã thông báo người dùng khi người dùng được tạo bằng Khi đăng nhập bằng Apple, bạn phải yêu cầu người dùng đăng nhập lại trước khi thu hồi quyền truy cập của họ mã thông báo và xoá tài khoản.

    Swift

    private func deleteCurrentUser() {
      do {
        let nonce = try CryptoUtils.randomNonceString()
        currentNonce = nonce
        let appleIDProvider = ASAuthorizationAppleIDProvider()
        let request = appleIDProvider.createRequest()
        request.requestedScopes = [.fullName, .email]
        request.nonce = CryptoUtils.sha256(nonce)
    
        let authorizationController = ASAuthorizationController(authorizationRequests: [request])
        authorizationController.delegate = self
        authorizationController.presentationContextProvider = self
        authorizationController.performRequests()
      } catch {
        // In the unlikely case that nonce generation fails, show error view.
        displayError(error)
      }
    }
    
    
  3. Lấy mã uỷ quyền từ ASAuthorizationAppleIDCredential và sử dụng hàm này để gọi Auth.auth().revokeToken(withAuthorizationCode:) nhằm thu hồi mã thông báo của người dùng.

    Swift

    func authorizationController(controller: ASAuthorizationController,
                                 didCompleteWithAuthorization authorization: ASAuthorization) {
      guard let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential
      else {
        print("Unable to retrieve AppleIDCredential")
        return
      }
    
      guard let _ = currentNonce else {
        fatalError("Invalid state: A login callback was received, but no login request was sent.")
      }
    
      guard let appleAuthCode = appleIDCredential.authorizationCode else {
        print("Unable to fetch authorization code")
        return
      }
    
      guard let authCodeString = String(data: appleAuthCode, encoding: .utf8) else {
        print("Unable to serialize auth code string from data: \(appleAuthCode.debugDescription)")
        return
      }
    
      Task {
        do {
          try await Auth.auth().revokeToken(withAuthorizationCode: authCodeString)
          try await user?.delete()
          self.updateUI()
        } catch {
          self.displayError(error)
        }
      }
    }
    
    
  4. Cuối cùng, hãy xoá tài khoản người dùng (và tất cả dữ liệu liên quan)

Các 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à được liên kết với thông tin đăng nhập—tức là tên người dùng và mật khẩu, số điện thoại số hoặc thông tin của nhà cung cấp dịch vụ xác thực – người dùng đã đăng nhập. Thông tin mới này được lưu trữ như một phần của dự án Firebase và có thể được dùng để xác định một 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 ứng dụng của mình, bạn có thể lấy thông tin hồ sơ cơ bản của người dùng từ User . Xem phần Quản lý người dùng.

  • Trong Cơ sở dữ liệu theo thời gian thực của Firebase và Cloud Storage Quy tắc bảo mật, bạn có thể lấy mã nhận dạng người dùng duy nhất của người dùng đã đăng nhập từ biến auth, để kiểm soát loại dữ liệu mà 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 mình bằng nhiều phương thức xác thực bằng cách liên kết thông tin đăng nhập của nhà cung cấp dịch vụ xác thực với tài khoản người dùng hiện có.

Để đăng xuất một người dùng, hãy gọi signOut:.

Swift

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 nên thêm mã xử lý lỗi cho toàn bộ phạm vi xác thực . Hãy xem bài viết Xử lý lỗi.