Memproses login pengguna dengan Apple di iOS

Dokumen ini menunjukkan cara menggunakan Identity Platform untuk menambahkan Sign in with Apple ke aplikasi iOS Anda.

Sebelum memulai

Mengonfigurasi aplikasi dengan Apple

Di situs Apple Developer:

  1. Aktifkan kemampuan Login dengan Apple untuk aplikasi Anda.

  2. Jika Anda menggunakan Identity Platform untuk mengirim email kepada pengguna, konfigurasi project Anda dengan layanan relai email pribadi Apple menggunakan email berikut:

    noreply@project-id.firebaseapp.com
    

    Anda juga dapat menggunakan template email khusus, jika aplikasi Anda memilikinya.

Mematuhi persyaratan data anonim Apple

Apple memberi pengguna opsi untuk menganonimkan data mereka, termasuk alamat email mereka. Apple menetapkan alamat email yang di-obfuscate kepada pengguna yang memilih opsi ini dengan domain privaterelay.appleid.com.

Aplikasi Anda harus mematuhi kebijakan atau persyaratan developer yang berlaku dari Apple terkait ID Apple anonim. Hal ini termasuk memperoleh izin pengguna sebelum mengaitkan informasi identitas pribadi (PII) dengan ID Apple anonim. Tindakan yang melibatkan PII mencakup, tetapi tidak terbatas pada:

  • Menautkan alamat email ke ID Apple anonim, atau sebaliknya.
  • Menautkan nomor telepon ke ID Apple anonim, atau sebaliknya
  • Menautkan kredensial sosial non-anonim, seperti Facebook atau Google, ke ID Apple anonim, atau sebaliknya.

Untuk informasi selengkapnya, lihat Perjanjian Lisensi Program Developer Apple untuk akun developer Apple Anda.

Mengonfigurasi Apple sebagai penyedia

Untuk mengonfigurasi Apple sebagai penyedia identitas:

  1. Buka halaman Identity Provider di Konsol Google Cloud.

    Buka halaman Penyedia Identitas

  2. Klik Tambahkan Penyedia.

  3. Pilih Apple dari daftar.

  4. Di bagian Platform, pilih iOS.

  5. Masukkan ID Paket aplikasi Anda.

  6. Daftarkan domain aplikasi Anda dengan mengklik Add domain di bagian Authorized Domains. Untuk tujuan pengembangan, localhost sudah diaktifkan secara default.

  7. Di bagian Konfigurasi aplikasi Anda, klik iOS. Salin cuplikan tersebut ke kode aplikasi Anda untuk menginisialisasi Client SDK Identity Platform.

  8. Klik Save.

Memproses login pengguna dengan SDK Klien

  1. Proses login pengguna dan dapatkan token ID menggunakan framework Authentication Services Apple.

  2. Buat string acak, yang dikenal sebagai nonce, dengan memanggil SecRandomCopyBytes(_:_:_:).

    Nonce digunakan untuk mencegah serangan replay. Anda menyertakan hash SHA-256 nonce dalam permintaan autentikasi, dan Apple menampilkannya, tanpa dimodifikasi, dalam respons. Identity Platform kemudian memvalidasi respons tersebut dengan membandingkan hash asli dengan nilai yang ditampilkan oleh Apple.

  3. Mulai alur login Apple, termasuk hash SHA-256 nonce yang Anda buat pada langkah sebelumnya, dan class delegasi untuk menangani respons Apple:

    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()
    }
    
    @available(iOS 13, *)
    private func sha256(_ input: String) -> String {
      let inputData = Data(input.utf8)
      let hashedData = SHA256.hash(data: inputData)
      let hashString = hashedData.compactMap {
        return String(format: "%02x", $0)
      }.joined()
    
      return hashString
    }
    

    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];
    }
    
    - (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;
    }
    
  4. Tangani respons Apple dalam penerapan ASAuthorizationControllerDelegate Anda. Jika login berhasil, gunakan token ID dari respons Apple dengan nonce tanpa hash untuk diautentikasi dengan Identity Platform:

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

Tidak seperti banyak penyedia identitas lainnya, Apple tidak memberikan URL foto.

Jika pengguna memilih untuk tidak membagikan email asli mereka ke aplikasi Anda, Apple akan menyediakan alamat email unik untuk dibagikan oleh pengguna tersebut. Email ini memiliki format xyz@privaterelay.appleid.com. Jika Anda mengonfigurasi layanan relai email pribadi, Apple akan meneruskan email yang dikirim ke alamat anonim tersebut ke alamat email asli pengguna.

Apple hanya membagikan informasi pengguna, seperti nama tampilan, kepada aplikasi saat pengguna login untuk pertama kalinya. Dalam sebagian besar kasus, Identity Platform menyimpan data ini, yang memungkinkan Anda mengambilnya menggunakan firebase.auth().currentUser.displayName selama sesi mendatang. Namun, jika Anda mengizinkan pengguna untuk login ke aplikasi menggunakan Apple sebelum berintegrasi dengan Identity Platform, informasi pengguna tidak akan tersedia.

Penghapusan akun pengguna

Apple mewajibkan aplikasi iOS yang mendukung pembuatan akun juga harus mengizinkan pengguna untuk memulai penghapusan akun dari dalam aplikasi.

Saat menghapus akun pengguna, Anda harus mencabut token pengguna sebelum menghapus akun pengguna tersebut, serta semua data yang Anda simpan untuknya di Firestore, Cloud Storage, dan Firebase Realtime Database. Untuk mengetahui informasi selengkapnya, lihat Menawarkan penghapusan akun di aplikasi dalam dokumentasi dukungan developer Apple.

Karena Identity Platform tidak menyimpan token pengguna saat pengguna dibuat dengan login dengan Apple, Anda harus meminta pengguna untuk login sebelum mencabut token dan menghapus akun. Atau, guna menghindari permintaan pengguna untuk login lagi saat pengguna login dengan login dengan Apple, Anda dapat menyimpan kode otorisasi untuk digunakan kembali selama pencabutan token.

Untuk mencabut token pengguna dan menghapus akunnya, jalankan perintah berikut:

Swift

let user = Auth.auth().currentUser

// Check if the user has a token.
if let providerData = user?.providerData {
  for provider in providerData {
    guard let provider = provider as? FIRUserInfo else {
      continue
    }
    if provider.providerID() == "apple.com" {
      isAppleProviderLinked = true
    }
  }
}

// Re-authenticate the user and revoke their token
if isAppleProviderLinked {
  let request = appleIDRequest(withState: "revokeAppleTokenAndDeleteUser")
  let controller = ASAuthorizationController(authorizationRequests: [request])
  controller.delegate = self
  controller.presentationContextProvider = self
  controller.performRequests()
} else {
  // Usual user deletion
}

func authorizationController(
  controller: ASAuthorizationController,
  didCompleteWithAuthorization authorization: ASAuthorization
) {
  if authorization.credential is ASAuthorizationAppleIDCredential {
    let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential
    if authorization.credential is ASAuthorizationAppleIDCredential {
      if appleIDCredential.state == "signIn" {
        // Sign in with Firebase.
        // ...
      } else if appleIDCredential.state == "revokeAppleTokenAndDeleteUser" {
        // Revoke token with Firebase.
        Auth.auth().revokeTokenWithAuthorizationCode(code) { error in
          if error != nil {
            // Token revocation failed.
          } else {
            // Token revocation succeeded then delete user again.
            let user = Auth.auth().currentUser
            user?.delete { error in
              // ...
            }
          }

        }
      }
    }
  }
}

Objective-C

FIRUser *user = [FIRAuth auth].currentUser;

// Check if the user has a token.
BOOL isAppleProviderLinked = false;
for (id<FIRUserInfo> provider in user.providerData) {
  if ([[provider providerID] isEqual:@"apple.com"]) {
    isAppleProviderLinked = true;
  }
}

// Re-authenticate the user and revoke their token
if (isAppleProviderLinked) {
  if (@available(iOS 13, *)) {
    ASAuthorizationAppleIDRequest *request =
        [self appleIDRequestWithState:@"revokeAppleTokenAndDeleteUser"];
    ASAuthorizationController *controller = [[ASAuthorizationController alloc]
        initWithAuthorizationRequests:@[ request ]];
    controller.delegate = self;
    controller.presentationContextProvider = self;
    [controller performRequests];
  }
} else {
  // Usual user deletion
}

- (void)authorizationController:(ASAuthorizationController *)controller
    didCompleteWithAuthorization:(ASAuthorization *)authorization
    API_AVAILABLE(ios(13.0)) {
  if ([authorization.credential
          isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
    ASAuthorizationAppleIDCredential *appleIDCredential =
        authorization.credential;

    if ([appleIDCredential.state isEqualToString:@"signIn"]) {
      // Sign in with Firebase.
      // ...
    } else if ([appleIDCredential.state
                  isEqualToString:@"revokeAppleTokenAndDeleteUser"]) {
      // Revoke token with Firebase.
      NSString *code =
          [[NSString alloc] initWithData:appleIDCredential.authorizationCode
                                encoding:NSUTF8StringEncoding];
      [[FIRAuth auth]
          revokeTokenWithAuthorizationCode:code
                                completion:^(NSError *_Nullable error) {
                                  if (error != nil) {
                                    // Token revocation failed.
                                  } else {
                                    // Token revocation succeeded then delete
                                    // user again.
                                    FIRUser *user = [FIRAuth auth].currentUser;
                                    [user deleteWithCompletion:^(
                                              NSError *_Nullable error){
                                        // ...
                                    }];
                                  }
                                }];
    }
  }
}

Langkah selanjutnya