Melakukan Autentikasi Menggunakan Apple di iOS

Anda dapat mengizinkan pengguna melakukan autentikasi dengan Firebase melalui ID Apple pengguna tersebut menggunakan Firebase SDK untuk menjalankan alur login OAuth 2.0 end-to-end.

Sebelum memulai

Untuk login menggunakan Apple, pertama-tama konfigurasikan Login dengan Apple di situs developer Apple, lalu aktifkan Apple sebagai penyedia login untuk project Firebase Anda.

Bergabunglah dengan Program Developer Apple

Login dengan Apple hanya dapat dikonfigurasi oleh anggota Program Developer Apple.

Mengonfigurasi Login dengan Apple

  1. Aktifkan Login dengan Apple untuk aplikasi Anda di halaman Certificates, Identifiers & Profiles dari situs developer Apple.
  2. Jika Anda menggunakan salah satu fitur Firebase Authentication yang mengirimkan email kepada pengguna, termasuk login dengan link email, verifikasi alamat email, pembatalan perubahan akun, dan lainnya, konfigurasikan layanan relai email pribadi Apple lalu daftarkan noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (atau domain template email yang disesuaikan) agar Apple dapat merelai email yang dikirim oleh Firebase Authentication ke alamat email Apple yang dianonimkan.

Mengaktifkan Apple sebagai penyedia login

  1. Tambahkan Firebase ke project iOS. Pastikan untuk mendaftarkan ID paket aplikasi Anda saat menyiapkan aplikasi di Firebase console.
  2. Di Firebase console, buka bagian Auth. Pada tab Metode login, aktifkan penyedia Apple. Jika Anda hanya menggunakan Login dengan Apple di aplikasi iOS, Anda dapat mengosongkan kolom ID Layanan, ID Tim Apple, kunci pribadi, dan ID kunci.

Mematuhi persyaratan data anonim Apple

Login dengan Apple memberi pengguna opsi untuk menganonimkan data miliknya, termasuk alamat email, saat login. Pengguna yang memilih opsi ini memiliki alamat email dengan domain privaterelay.appleid.com. Saat Anda menggunakan Login dengan Apple di aplikasi, Anda harus mematuhi kebijakan atau persyaratan developer yang berlaku dari Apple terkait ID Apple yang dianonimkan ini.

Hal ini termasuk memperoleh persetujuan pengguna yang diperlukan sebelum Anda mengaitkan informasi identitas pribadi apa pun langsung dengan ID Apple yang dianonimkan. Penggunaan Firebase Authentication dapat meliputi tindakan-tindakan berikut:

  • Menautkan alamat email ke ID Apple yang dianonimkan atau sebaliknya.
  • Menautkan nomor telepon ke ID Apple yang dianonimkan atau sebaliknya
  • Menautkan kredensial sosial non-anonim (Facebook, Google, dsb.) ke ID Apple yang dianonimkan atau sebaliknya.

Daftar di atas tidak lengkap. Baca Perjanjian Lisensi Program Developer Apple di bagian Keanggotaan akun developer untuk memastikan aplikasi Anda memenuhi persyaratan Apple.

Login dengan Apple dan autentikasi dengan Firebase

Untuk mengautentikasi dengan akun Apple, pertama-tama buat pengguna login ke akun Apple-nya menggunakan framework AuthenticationServices Apple, lalu gunakan token ID dari tanggapan Apple untuk membuat objek AuthCredential Firebase:

  1. Untuk setiap permintaan proses login, buat string acak — “nonce” —yang akan Anda gunakan untuk memastikan bahwa token ID yang Anda dapatkan khusus diberikan sebagai tanggapan atas permintaan autentikasi aplikasi Anda. Langkah ini penting untuk mencegah terjadinya serangan replay.

    Anda dapat membuat nonce kriptografi yang aman di iOS dengan SecRandomCopyBytes(_:_:_), seperti pada contoh berikut:

    Swift

    // 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: Array<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

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

    Anda akan mengirimkan hash SHA256 nonce bersama permintaan login, yang tidak akan diubah oleh Apple dalam responsnya. Firebase akan memvalidasi respons tersebut dengan melakukan hashing nonce yang asli dan membandingkannya dengan nilai yang diteruskan oleh Apple.

  2. Mulai jalankan alur login Apple, termasuk di permintaan Anda, hash SHA256 nonce dan class delegasi yang akan menangani respons Apple (baca langkah berikutnya):

    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;
    }
    
  3. Menangani respons Apple dalam penerapan ASAuthorizationControllerDelegate Anda. Jika proses login berhasil, gunakan token ID dari respons Apple dengan nonce yang tidak di-hash untuk diautentikasi dengan 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.
          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 penyedia lain yang didukung oleh Firebase Auth, Apple tidak memberikan URL foto.

Selain itu, jika pengguna memilih untuk tidak berbagi email dengan aplikasi, Apple akan menyediakan alamat email unik untuk pengguna tersebut (dari formulir xyz@privaterelay.appleid.com), yang dibagikan dengan aplikasi Anda. Jika Anda mengonfigurasi layanan relai email pribadi, Apple akan meneruskan email yang dikirim ke alamat anonim ke alamat email asli pengguna.

Apple hanya membagikan informasi pengguna seperti nama yang ditampilkan dengan aplikasi saat pengguna login untuk pertama kalinya. Biasanya, Firebase menyimpan nama yang ditampilkan saat pengguna login dengan Apple untuk pertama kalinya, yang dapat Anda peroleh dengan Auth.auth().currentUser.displayName. Namun, jika sebelumnya Anda menggunakan Apple untuk membuat pengguna login ke aplikasi tanpa menggunakan Firebase, Apple tidak akan memberikan nama yang ditampilkan kepada Firebase.

Autentikasi ulang dan penautan akun

Pola yang sama dapat digunakan dengan reauthenticateWithCredential(), yang dapat Anda gunakan untuk mengambil kredensial baru untuk operasi sensitif yang memerlukan login terbaru:

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

Selain itu, Anda dapat menggunakan linkWithCredential() untuk menautkan penyedia identitas yang berbeda ke akun yang ada.

Perlu diperhatikan bahwa Apple mewajibkan Anda untuk mendapatkan persetujuan eksplisit dari pengguna sebelum Anda menautkan akun Apple mereka ke data lain.

Dengan menggunakan fitur login dengan Apple, Anda tidak akan bisa menggunakan kembali kredensial autentikasi untuk menautkan ke akun yang ada. Jika Anda ingin menautkan kredensial Login dengan Apple ke akun lain, Anda harus terlebih dulu mencoba menautkan akun menggunakan kredensial Login dengan Apple yang lama dan memeriksa error yang ditampilkan untuk mendapatkan kredensial baru. Kredensial baru akan ditempatkan di kamus userInfo error tersebut dan dapat diakses melalui kunci FIRAuthErrorUserInfoUpdatedCredentialKey.

Misalnya, untuk menautkan akun Facebook ke akun Firebase saat ini, gunakan token akses yang Anda peroleh saat pengguna login ke 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.
  // ...
}];

Langkah berikutnya

Setelah pengguna login untuk pertama kalinya, akun pengguna baru akan dibuat dan ditautkan ke kredensial, yaitu nama pengguna dan sandi, nomor telepon, atau informasi penyedia autentikasi, yang digunakan pengguna tersebut untuk login. Akun baru ini disimpan sebagai bagian dari project Firebase Anda, dan dapat digunakan untuk mengidentifikasi pengguna di setiap aplikasi dalam project, terlepas dari cara pengguna login.

  • Di aplikasi yang dimiliki, Anda bisa mendapatkan informasi profil dasar pengguna dari objek FIRUser. Baca bagian Mengelola Pengguna.

  • Di Aturan Keamanan Firebase Realtime Database dan Cloud Storage, Anda bisa mendapatkan ID pengguna unik yang login dari variabel auth, dan menggunakannya untuk mengontrol data yang dapat diakses oleh pengguna.

Anda dapat mengizinkan pengguna untuk login ke aplikasi menggunakan beberapa penyedia autentikasi dengan menautkan kredensial penyedia autentikasi ke akun pengguna yang ada.

Agar pengguna logout, panggil 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;
}

Anda juga dapat menambahkan kode penanganan error untuk berbagai error autentikasi. Baca bagian Menangani Error.