Melakukan Autentikasi Menggunakan Apple di Android

Anda dapat mengizinkan pengguna melakukan autentikasi dengan Firebase menggunakan ID Apple mereka dengan Firebase SDK untuk menjalankan alur login OAuth 2.0 secara menyeluruh.

Sebelum memulai

Agar pengguna dapat 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

Di situs Apple Developer, lakukan hal berikut:

  1. Kaitkan situs dengan aplikasi seperti yang dijelaskan di bagian pertama artikel Mengonfigurasi Login dengan Apple untuk web. Jika diminta, daftarkan URL berikut sebagai Return URL:

    https://YOUR_FIREBASE_PROJECT_ID.firebaseapp.com/__/auth/handler

    Anda bisa mendapatkan project ID Firebase di halaman setelan Firebase console.

    Setelah selesai, catat Service ID baru, yang akan diperlukan nanti.

  2. Membuat kunci pribadi Login dengan Apple. Anda akan memerlukan kunci pribadi dan ID kunci baru nanti.
  3. 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 Android Anda. Pastikan untuk mendaftarkan tanda tangan SHA-1 aplikasi saat Anda menyiapkan aplikasi di Firebase console.
  2. Di Firebase console, buka bagian Auth. Pada tab Sign-in method, aktifkan penyedia Apple. Tentukan Service ID yang Anda buat di bagian sebelumnya. Selain itu, di bagian OAuth code flow configuration, masukkan Team ID Apple, kunci pribadi, dan ID kunci yang Anda buat di bagian sebelumnya.

Mematuhi persyaratan data anonim Apple

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

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

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

Daftar di atas tidak lengkap. Baca Apple Developer Program License Agreement di bagian Membership akun developer Anda untuk memastikan aplikasi Anda memenuhi persyaratan Apple.

Menangani alur login dengan Firebase SDK

Di Android, cara termudah untuk mengautentikasi pengguna dengan Firebase menggunakan akun Apple mereka adalah dengan menangani seluruh alur login dengan Firebase Android SDK.

Untuk menangani alur login dengan Firebase Android SDK, ikuti langkah-langkah berikut:

  1. Buat instance OAuthProvider menggunakan Builder-nya dengan ID penyedia apple.com:

    Kotlin

    val provider = OAuthProvider.newBuilder("apple.com")
    

    Java

    OAuthProvider.Builder provider = OAuthProvider.newBuilder("apple.com");
    
  2. Opsional: Tentukan cakupan OAuth 2.0 tambahan selain default yang ingin diminta dari penyedia autentikasi.

    Kotlin

    provider.setScopes(arrayOf("email", "name"))
    

    Java

    List<String> scopes =
        new ArrayList<String>() {
          {
            add("email");
            add("name");
          }
        };
    provider.setScopes(scopes);
    

    Secara default, jika One account per email address diaktifkan, Firebase akan meminta cakupan email dan nama. Jika Anda mengubah setelan menjadi Multiple accounts per email address, Firebase tidak akan meminta cakupan apa pun dari Apple kecuali jika Anda menentukannya.

  3. Opsional: Jika ingin menampilkan layar login Apple dalam bahasa, selain bahasa Inggris, tetapkan locale parameter. Baca dokumentasi Login dengan Apple untuk lokalitas yang didukung.

    Kotlin

    // Localize the Apple authentication screen in French.
    provider.addCustomParameter("locale", "fr")
    

    Java

    // Localize the Apple authentication screen in French.
    provider.addCustomParameter("locale", "fr");
    
  4. Lakukan autentikasi Firebase menggunakan objek penyedia OAuth. Perlu diperhatikan bahwa tidak seperti operasi FirebaseAuth lainnya, operasi ini akan mengambil alih kendali UI Anda dengan memunculkan Tab Chrome Kustom. Oleh sebab itu, jangan merujuk Aktivitas di OnSuccessListener dan OnFailureListener yang dikaitkan karena kaitan tersebut akan langsung dilepas ketika operasi memulai UI.

    Anda harus terlebih dahulu memeriksa apakah Anda sudah menerima respons atau belum. Proses login dengan metode ini menempatkan Aktivitas Anda di latar belakang, yang berarti aktivitas tersebut dapat diklaim kembali oleh sistem selama alur login. Untuk memastikan bahwa Anda tidak membuat pengguna mencoba lagi jika hal ini terjadi, Anda harus memeriksa apakah hasilnya sudah ada atau belum.

    Untuk memeriksa apakah ada hasil yang tertunda, panggil getPendingAuthResult():

    Kotlin

    val pending = auth.pendingAuthResult
    if (pending != null) {
        pending.addOnSuccessListener { authResult ->
            Log.d(TAG, "checkPending:onSuccess:$authResult")
            // Get the user profile with authResult.getUser() and
            // authResult.getAdditionalUserInfo(), and the ID
            // token from Apple with authResult.getCredential().
        }.addOnFailureListener { e ->
            Log.w(TAG, "checkPending:onFailure", e)
        }
    } else {
        Log.d(TAG, "pending: null")
    }
    

    Java

    mAuth = FirebaseAuth.getInstance();
    Task<AuthResult> pending = mAuth.getPendingAuthResult();
    if (pending != null) {
        pending.addOnSuccessListener(new OnSuccessListener<AuthResult>() {
            @Override
            public void onSuccess(AuthResult authResult) {
                Log.d(TAG, "checkPending:onSuccess:" + authResult);
                // Get the user profile with authResult.getUser() and
                // authResult.getAdditionalUserInfo(), and the ID
                // token from Apple with authResult.getCredential().
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                Log.w(TAG, "checkPending:onFailure", e);
            }
        });
    } else {
        Log.d(TAG, "pending: null");
    }
    

    Jika tidak ada hasil yang tertunda, mulai alur login, dengan memanggil startActivityForSignInWithProvider():

    Kotlin

    auth.startActivityForSignInWithProvider(this, provider.build())
            .addOnSuccessListener { authResult ->
                // Sign-in successful!
                Log.d(TAG, "activitySignIn:onSuccess:${authResult.user}")
                val user = authResult.user
                // ...
            }
            .addOnFailureListener { e ->
                Log.w(TAG, "activitySignIn:onFailure", e)
            }
    

    Java

    mAuth.startActivityForSignInWithProvider(this, provider.build())
            .addOnSuccessListener(
                    new OnSuccessListener<AuthResult>() {
                        @Override
                        public void onSuccess(AuthResult authResult) {
                            // Sign-in successful!
                            Log.d(TAG, "activitySignIn:onSuccess:" + authResult.getUser());
                            FirebaseUser user = authResult.getUser();
                            // ...
                        }
                    })
            .addOnFailureListener(
                    new OnFailureListener() {
                        @Override
                        public void onFailure(@NonNull Exception e) {
                            Log.w(TAG, "activitySignIn:onFailure", e);
                        }
                    });
    

    Tidak seperti penyedia lain yang didukung oleh Firebase Auth, Apple tidak memberikan URL foto.

    Selain itu, jika pengguna memilih untuk tidak membagikan email ke aplikasi, Apple akan menyediakan alamat email unik untuk pengguna tersebut (dengan format xyz@privaterelay.appleid.com), yang dibagikan ke aplikasi Anda. 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. Biasanya, Firebase menyimpan nama tampilan saat pengguna login dengan Apple untuk pertama kalinya, yang dapat Anda peroleh dengan getCurrentUser().getDisplayName(). Namun, jika sebelumnya Anda menggunakan Apple untuk memproses login ke aplikasi tanpa menggunakan Firebase, Apple tidak akan memberikan nama tampilan pengguna ke Firebase.

Autentikasi ulang dan penautan akun

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

Kotlin

// The user is already signed-in.
val firebaseUser = auth.getCurrentUser()

firebaseUser
    .startActivityForReauthenticateWithProvider(/* activity= */ this, provider.build())
    .addOnSuccessListener( authResult -> {
        // User is re-authenticated with fresh tokens and
        // should be able to perform sensitive operations
        // like account deletion and email or password
        // update.
    })
    .addOnFailureListener( e -> {
        // Handle failure.
    })

Java

// The user is already signed-in.
FirebaseUser firebaseUser = mAuth.getCurrentUser();

firebaseUser
    .startActivityForReauthenticateWithProvider(/* activity= */ this, provider.build())
    .addOnSuccessListener(
        new OnSuccessListener<AuthResult>() {
          @Override
          public void onSuccess(AuthResult authResult) {
            // User is re-authenticated with fresh tokens and
            // should be able to perform sensitive operations
            // like account deletion and email or password
            // update.
          }
        })
    .addOnFailureListener(
        new OnFailureListener() {
          @Override
          public void onFailure(@NonNull Exception e) {
            // Handle failure.
          }
        });

Selain itu, Anda dapat menggunakan linkWithCredential() untuk menautkan berbagai penyedia identitas 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.

Misalnya, untuk menautkan akun Facebook ke akun Firebase saat ini, gunakan token akses yang Anda peroleh saat pengguna login ke Facebook:

Kotlin

// Initialize a Facebook credential with a Facebook access token.
val credential = FacebookAuthProvider.getCredential(token.getToken())

// Assuming the current user is an Apple user linking a Facebook provider.
mAuth.getCurrentUser().linkWithCredential(credential)
    .addOnCompleteListener(this, task -> {
        if (task.isSuccessful()) {
          // Facebook credential is linked to the current Apple user.
          // The user can now sign in to the same account
          // with either Apple or Facebook.
        }
      });

Java

// Initialize a Facebook credential with a Facebook access token.
AuthCredential credential = FacebookAuthProvider.getCredential(token.getToken());

// Assuming the current user is an Apple user linking a Facebook provider.
mAuth.getCurrentUser().linkWithCredential(credential)
    .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
      @Override
      public void onComplete(@NonNull Task<AuthResult> task) {
        if (task.isSuccessful()) {
          // Facebook credential is linked to the current Apple user.
          // The user can now sign in to the same account
          // with either Apple or Facebook.
        }
      }
    });

Lanjutan: Menangani alur login secara manual

Anda juga dapat melakukan autentikasi dengan Firebase menggunakan Akun Apple dengan menangani alur login menggunakan Apple Sign-In JS SDK, mem-build alur OAuth secara manual, atau menggunakan library OAuth seperti AppAuth.

  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 respons atas permintaan autentikasi aplikasi Anda. Langkah ini penting untuk mencegah terjadinya serangan replay.

    Anda dapat membuat nonce yang aman dari sisi kriptografi di Android dengan SecureRandom, seperti pada contoh berikut:

    Kotlin

    private fun generateNonce(length: Int): String {
        val generator = SecureRandom()
    
        val charsetDecoder = StandardCharsets.US_ASCII.newDecoder()
        charsetDecoder.onUnmappableCharacter(CodingErrorAction.IGNORE)
        charsetDecoder.onMalformedInput(CodingErrorAction.IGNORE)
    
        val bytes = ByteArray(length)
        val inBuffer = ByteBuffer.wrap(bytes)
        val outBuffer = CharBuffer.allocate(length)
        while (outBuffer.hasRemaining()) {
            generator.nextBytes(bytes)
            inBuffer.rewind()
            charsetDecoder.reset()
            charsetDecoder.decode(inBuffer, outBuffer, false)
        }
        outBuffer.flip()
        return outBuffer.toString()
    }
    

    Java

    private String generateNonce(int length) {
        SecureRandom generator = new SecureRandom();
    
        CharsetDecoder charsetDecoder = StandardCharsets.US_ASCII.newDecoder();
        charsetDecoder.onUnmappableCharacter(CodingErrorAction.IGNORE);
        charsetDecoder.onMalformedInput(CodingErrorAction.IGNORE);
    
        byte[] bytes = new byte[length];
        ByteBuffer inBuffer = ByteBuffer.wrap(bytes);
        CharBuffer outBuffer = CharBuffer.allocate(length);
        while (outBuffer.hasRemaining()) {
            generator.nextBytes(bytes);
            inBuffer.rewind();
            charsetDecoder.reset();
            charsetDecoder.decode(inBuffer, outBuffer, false);
        }
        outBuffer.flip();
        return outBuffer.toString();
    }
    

    Lalu, dapatkan hash SHA246 nonce tersebut sebagai string hex:

    Kotlin

    private fun sha256(s: String): String {
        val md = MessageDigest.getInstance("SHA-256")
        val digest = md.digest(s.toByteArray())
        val hash = StringBuilder()
        for (c in digest) {
            hash.append(String.format("%02x", c))
        }
        return hash.toString()
    }
    

    Java

    private String sha256(String s) throws NoSuchAlgorithmException {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] digest = md.digest(s.getBytes());
        StringBuilder hash = new StringBuilder();
        for (byte c: digest) {
            hash.append(String.format("%02x", c));
        }
        return hash.toString();
    }
    

    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 alur login Apple menggunakan library OAuth atau metode lainnya. Pastikan untuk menyertakan nonce hasil hash sebagai parameter dalam permintaan Anda.

  3. Setelah menerima respons Apple, dapatkan token ID dari respons tersebut lalu gunakan token ID tersebut dan nonce yang tidak di-hash untuk membuat AuthCredential:

    Kotlin

    val credential =  OAuthProvider.newCredentialBuilder("apple.com")
        .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce)
        .build()
    

    Java

    AuthCredential credential =  OAuthProvider.newCredentialBuilder("apple.com")
        .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce)
        .build();
    
  4. Lakukan autentikasi dengan Firebase menggunakan kredensial Firebase:

    Kotlin

    auth.signInWithCredential(credential)
          .addOnCompleteListener(this) { task ->
              if (task.isSuccessful) {
                // User successfully signed in with Apple ID token.
                // ...
              }
          }
    

    Java

    mAuth.signInWithCredential(credential)
        .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
          @Override
          public void onComplete(@NonNull Task<AuthResult> task) {
            if (task.isSuccessful()) {
              // User successfully signed in with Apple ID token.
              // ...
            }
          }
        });
    

Jika panggilan ke signInWithCredential berhasil, Anda dapat menggunakan metode getCurrentUser untuk mendapatkan data akun pengguna.

Pencabutan Token

Apple mewajibkan aplikasi yang mendukung pembuatan akun harus mengizinkan pengguna untuk memulai penghapusan akun di dalam aplikasi, seperti yang dijelaskan dalam Panduan Ulasan App Store

Selain itu, aplikasi yang mendukung Login dengan Apple harus menggunakan REST API Login dengan Apple untuk mencabut token pengguna.

Untuk memenuhi persyaratan ini, terapkan langkah-langkah berikut:

  1. Gunakan metode startActivityForSignInWithProvider() untuk login menggunakan Apple dan mendapatkan AuthResult.

  2. Dapatkan token akses untuk penyedia Apple.

    Kotlin

    val oauthCredential: OAuthCredential =  authResult.credential
    val accessToken = oauthCredential.accessToken
    

    Java

    OAuthCredential oauthCredential = (OAuthCredential) authResult.getCredential();
    String accessToken = oauthCredential.getAccessToken();
    
  3. Cabut token menggunakan revokeAccessToken API.

    Kotlin

    mAuth.revokeAccessToken(accessToken)
      .addOnCompleteListener(this) { task ->
        if (task.isSuccessful) {
          // Access token successfully revoked
          // for the user ...
        }
    }
    

    Java

    mAuth.revokeAccessToken(accessToken)
        .addOnCompleteListener(this, new OnCompleteListener<Void>() {
            @Override
            public void onComplete(@NonNull Task<Void> task) {
              if (task.isSuccessful()) {
                // Access token successfully revoked
                // for the user ...
              }
            }
      });
    
  1. Terakhir, hapus akun pengguna (dan semua data terkait)

    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, Anda bisa mendapatkan informasi profil dasar pengguna dari objek FirebaseUser. Baca bagian Mengelola Pengguna.

    • Di Aturan Keamanan Firebase Realtime Database dan Cloud Storage, Anda bisa mendapatkan ID pengguna unik milik pengguna 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.

    Untuk memproses logout pengguna, panggil signOut:

    Kotlin

    Firebase.auth.signOut()

    Java

    FirebaseAuth.getInstance().signOut();