Xác thực bằng Apple

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 toàn diện.

Trước khi bắt đầu

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

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

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

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

  1. Bật Đăng nhập bằng Apple cho ứng dụng của bạn trên trang Chứng chỉ, Số nhận dạng & Cấu hình trên trang web dành cho nhà phát triển của Apple.
  2. Liên kết trang web của bạn với ứng dụng của bạn như được mô tả trong phần đầu tiên của Định cấu hình Đăng nhập với Apple cho web . Khi được nhắc, hãy đăng ký URL sau làm URL trả về:
    https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler
    Bạn có thể lấy ID dự án Firebase của mình trên trang cài đặt bảng điều khiển Firebase . Khi bạn hoàn tất, hãy ghi lại ID dịch vụ mới của mình, bạn sẽ cần ID này trong phần tiếp theo.
  3. Tạo Đăng nhập bằng khóa riêng của Apple . Bạn sẽ cần khóa riêng và ID khóa mới 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 liên kết email, xác minh địa chỉ email, hủy bỏ thay đổi tài khoản và các tính năng 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 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 ẩn danh của Apple.

Bật 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 . Đảm bảo đăng ký ID gói của ứng dụng khi bạn thiết lập ứng dụng trong bảng điều khiển Firebase.
  2. Trong bảng điều khiển Firebase , hãy mở phần Auth . Trên tab Phương thức đăng nhập , bật nhà cung cấp Apple . Chỉ định ID dịch vụ bạn đã tạo trong phần trước. Ngoài ra, trong phần cấu hình luồng mã OAuth , hãy chỉ định ID nhóm Apple của bạn cũng như ID khóa và khóa riêng mà bạn đã tạo trong phần trước.

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

Đăng nhập bằng Apple cung cấp cho người dùng tùy chọn ẩ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 tùy chọn này có địa chỉ email với 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 liên quan đến các ID Apple ẩn danh này.

Điều này bao gồm việc lấy bất kỳ sự đồng ý bắt buộc nào của người dùng trước khi bạn liên kết bất kỳ thông tin nhận dạng cá nhân 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 xác thực 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 dành cho 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 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 khung Dịch AuthenticationServices của Apple, sau đó sử dụng mã thông báo ID từ phản hồi của Apple để tạo đối tượng AuthCredential Firebase:

  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"—mà bạn sẽ sử dụng để đảm bảo mã thông báo ID mà 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. Bước này rất quan trọng để ngăn chặn các cuộc tấn công lặp lại.

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

    Nhanh

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

    Mục tiêu-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, yêu cầu này sẽ được Apple 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ị do 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
    }
    
        

    Mục tiêu-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()
    }
    

    Mục tiêu-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 khi triển khai ASAuthorizationControllerDelegate của bạn. 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, 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)")
      }
    
    }
    

    Mục tiêu-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 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 ứng dụng, Apple sẽ cung cấp một địa chỉ email duy nhất cho người dùng đó (có dạng xyz@privaterelay.appleid.com ), địa chỉ này sẽ 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.

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

Có thể sử dụng cùng một mẫu với reauthenticateWithCredential() , bạn có thể sử dụng mẫu này để truy xuất thông tin xác thực mới cho các hoạt động nhạy cảm yêu cầu đă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.
  // ...
}

Mục tiêu-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 các nhà cung cấp danh tính khác nhau với các tài khoản hiện có.

Lưu ý rằng Apple yêu cầu bạn phải nhận đượ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 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 thông tin Đăng nhập cũ bằng thông tin đăng nhập Apple, sau đó kiểm tra lỗi được trả về để tìm thông tin đăng nhập mới. Thông tin xác thực mới sẽ nằm trong từ điển userInfo của lỗi và có thể được truy cập thông qua khóa 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ã thông báo truy cập mà bạn nhận được khi người dùng đăng nhập 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.
  // ...
}

Mục tiêu-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 xóa tài khoản của họ trong ứng dụng, như được mô tả trong Nguyên tắc đánh giá App Store

Để đáp ứng yêu cầu này, hãy thực hiện các bước sau:

  1. Đảm bảo rằng bạn đã điền vào phần cấu hình luồng mã OAuthID dịch vụ của cấu hình nhà cung cấp Đăng nhập bằng Apple, như được nêu trong phần Định cấu hình Đăng nhập bằng Apple .

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

    Nhanh

    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ã ủy quyền từ ASAuthorizationAppleIDCredential và sử dụng mã này để gọi Auth.auth().revokeToken(withAuthorizationCode:) để thu hồi mã thông báo của người dùng.

    Nhanh

    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, xóa tài khoản người dùng (và tất cả dữ liệu liên quan)

Bước tiếp theo

Sau khi người dùng đăng nhập lần đầu tiên, tài khoản người dùng mới đượ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 mình, bạn có thể lấy thông tin hồ sơ cơ bản của người dùng từ đối tượng User . Xem Quản lý người dùng .

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

Để đăng xuất người 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)
}

Mục tiêu-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 toàn bộ các lỗi xác thực. Xem Xử lý lỗi .