Xác thực bằng Apple trên Android

Bạn có thể cho phép người dùng xác thực với 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 của bạn.

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

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

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

Trên trang web Nhà phát triển Apple, hãy làm như sau:

  1. Liên kết trang web với ứng dụng của bạn như mô tả trong phần đầu tiên của bài viết Định cấu hình tính năng Đăng nhập bằng 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 mã dự án Firebase trên trang cài đặt bảng điều khiển của Firebase.

    Khi hoàn tất, hãy ghi lại Mã dịch vụ mới mà bạn sẽ cần trong phần tiếp theo.

  2. Tạo khoá riêng tư Đăng nhập bằng Apple. Bạn sẽ cần khoá riêng tư mới và mã khoá trong phần tiếp theo.
  3. 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, thu hồi 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 tuỳ chỉnh) để Apple có thể chuyển tiếp email do tính năng Xác thực Firebase gửi đến các địa chỉ email ẩn danh của Apple.

Cho phép Apple làm nhà cung cấp dịch vụ đăng nhập

  1. Thêm Firebase vào dự án Android của bạn. Hãy nhớ đăng ký chữ ký SHA-1 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ẻ Sign in method (Phương thức đăng nhập), hãy 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, hãy chỉ định mã nhóm Apple của bạn và khoá riêng tư cũng như mã khoá 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) khi đăng nhập. Những người dùng chọn tuỳ chọn này có địa chỉ email có miền privaterelay.appleid.com. Khi 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 hiện hành của Apple liên quan đến các mã nhận dạng Apple ẩn danh này.

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

  • Liên kết một địa chỉ email với ID Apple ẩn danh hoặc ngược lại.
  • Liên kết mộ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 mạng xã hội không ẩn danh (Facebook, Google, v.v.) với Apple ID ẩn danh hoặc ngược lại.

Danh sách bên trên chưa đầy đủ. Hãy tham khảo Thoả thuận cấp phép chương trình dành cho nhà phát triển của Apple trong mục 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.

Xử lý quy trình đăng nhập bằng SDK Firebase

Trên Android, cách dễ nhất để xác thực người dùng với Firebase bằng tài khoản Apple của họ là xử lý toàn bộ quy trình đăng nhập bằng SDK của Firebase cho Android.

Để xử lý quy trình đăng nhập bằng SDK Android của Firebase, hãy làm theo các bước sau:

  1. Tạo một thực thể của OAuthProvider bằng Trình tạo với mã nhận dạng nhà cung cấp apple.com:

    Kotlin+KTX

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

    Java

    OAuthProvider.Builder provider = OAuthProvider.newBuilder("apple.com");
    
  2. Không bắt buộc: Chỉ định các phạm vi OAuth 2.0 bổ sung ngoài phạm vi mặc định mà bạn muốn yêu cầu từ trình cung cấp dịch vụ xác thực.

    Kotlin+KTX

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

    Java

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

    Theo mặc định, khi bạn bật chế độ Một tài khoản cho mỗi địa chỉ email, Firebase sẽ yêu cầu phạm vi email và tên. Nếu bạn thay đổi chế độ cài đặt này thành Nhiều tài khoản cho mỗi địa chỉ email, thì Firebase sẽ không yêu cầu bất kỳ phạm vi nào từ Apple trừ phi bạn chỉ định các phạm vi đó.

  3. Không bắt buộc: Nếu bạn muốn hiển thị màn hình đăng nhập của Apple bằng một ngôn ngữ không phải là tiếng Anh, hãy đặt tham số locale. Xem tài liệu về Đăng nhập bằng Apple để biết các ngôn ngữ được hỗ trợ.

    Kotlin+KTX

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

    Java

    // Localize the Apple authentication screen in French.
    provider.addCustomParameter("locale", "fr");
    
  4. Xác thực bằng Firebase bằng cách sử dụng đối tượng nhà cung cấp OAuth. Xin lưu ý rằng không giống như các thao tác khác của FirebaseAuth, thao tác này sẽ kiểm soát giao diện người dùng bằng cách mở một Thẻ Chrome tuỳ chỉnh. Do đó, đừng tham chiếu đến Hoạt động của bạn trong OnSuccessListenerOnFailureListener mà bạn đính kèm vì chúng sẽ tách ra ngay lập tức khi thao tác này khởi động giao diện người dùng.

    Trước tiên, bạn nên kiểm tra xem mình đã nhận được phản hồi hay chưa. Việc đăng nhập bằng phương thức này sẽ đặt Hoạt động của bạn ở chế độ nền, nghĩa là hệ thống có thể thu hồi Hoạt động trong quá trình đăng nhập. Để đảm bảo rằng bạn không khiến người dùng thử lại nếu điều này xảy ra, bạn nên kiểm tra xem đã có kết quả hay chưa.

    Để kiểm tra xem có kết quả nào đang chờ xử lý hay không, hãy gọi getPendingAuthResult():

    Kotlin+KTX

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

    Nếu không có kết quả đang chờ xử lý nào, hãy bắt đầu quy trình đăng nhập bằng cách gọi startActivityForSignInWithProvider():

    Kotlin+KTX

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

    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 của ả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 đó (biểu mẫu xyz@privaterelay.appleid.com) để chia sẻ với ứng dụng. 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 email được gửi đến địa chỉ ẩn danh đến địa chỉ email thực của người dùng.

    Apple chỉ chia sẻ thông tin người dùng như tên hiển thị với các ứng dụng trong lần đầu tiên người dùng đăng nhập. Thông thường, Firebase lưu trữ tên hiển thị vào lần đầu tiên người dùng đăng nhập bằng Apple. Bạn có thể lấy tên này bằng getCurrentUser().getDisplayName(). Tuy nhiên, nếu trước đây bạn đã sử dụng Apple để đăng nhập người dùng vào ứng dụng mà không sử dụng Firebase, thì Apple sẽ không cung cấp Firebase cùng với tên hiển thị 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ể sử dụng cùng một mẫu với startActivityForReauthenticateWithProvider() để truy xuất thông tin đăng nhập mới cho các thao tác nhạy cảm yêu cầu đăng nhập gần đây:

Kotlin+KTX

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

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

Xin lưu ý rằng Apple yêu cầu bạn phải có được sự đồng ý rõ ràng của 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.

Ví dụ: để liên kết mộ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 bạn nhận được từ việc đăng nhập người dùng vào Facebook:

Kotlin+KTX

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

Nâng cao: Xử lý quy trình đăng nhập theo cách thủ công

Bạn cũng có thể xác thực với Firebase thông qua Tài khoản Apple bằng cách xử lý quy trình đăng nhập bằng cách sử dụng SDK JS để đăng nhập bằng Apple, tạo quy trình OAuth theo cách thủ công hoặc sử dụng thư viện OAuth như AppAuth.

  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 — "số chỉ dùng một lần" — mà bạn sẽ sử dụng để đảm bảo mã thông báo mã nhận dạng bạn nhận được đã được cấp riêng theo yêu cầu xác thực của ứng dụng. Đây là bước 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 bảo mật bằng mật mã trên Android bằng SecureRandom, như trong ví dụ sau:

    Kotlin+KTX

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

    Sau đó, hãy lấy hàm băm SHA246 của số chỉ dùng một lần dưới dạng chuỗi hex:

    Kotlin+KTX

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

    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 của mình. 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.

  2. Bắt đầu quy trình đăng nhập của Apple bằng thư viện OAuth hoặc phương thức khác. Hãy nhớ đưa số chỉ dùng một lần đã băm vào làm tham số trong yêu cầu của bạn.

  3. Sau khi bạn nhận được phản hồi của Apple, hãy lấy mã thông báo mã nhận dạng từ phản hồi rồi sử dụng mã đó cùng số chỉ dùng một lần chưa được băm để tạo AuthCredential:

    Kotlin+KTX

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

    Java

    AuthCredential credential =  OAuthProvider.newCredentialBuilder("apple.com")
        .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce)
        .build();
    
  4. Xác thực bằng Firebase bằng thông tin xác thực Firebase:

    Kotlin+KTX

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

Nếu lệnh gọi đến signInWithCredential thành công, bạn có thể sử dụng phương thức getCurrentUser để lấy dữ liệu tài khoản của người dùng.

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 tiến hành xoá tài khoản của họ trong ứng dụng đó, như mô tả trong Nguyên tắc về bài đánh giá trên App Store

Ngoài ra, các ứng dụng hỗ trợ tính năng Đăng nhập bằng Apple phải sử dụng API REST của tính năng Đăng nhập bằng Apple để thu hồi mã thông báo của người dùng.

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

  1. Sử dụng phương thức startActivityForSignInWithProvider() để đăng nhập bằng Apple và lấy AuthResult.

  2. Lấy mã truy cập của nhà cung cấp Apple.

    Kotlin+KTX

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

    Java

    OAuthCredential oauthCredential = (OAuthCredential) authResult.getCredential();
    String accessToken = oauthCredential.getAccessToken();
    
  3. Thu hồi mã thông báo bằng API revokeAccessToken.

    Kotlin+KTX

    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. 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, một tài khoản người dùng mới sẽ được tạo và 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 hoặc thông tin nhà cung cấp dịch vụ xác thực) mà người dùng đã đăng nhập. Tài khoản mới này được lưu trữ trong dự án Firebase và có thể 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 FirebaseUser. Xem phần Quản lý người dùng.

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

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

    Kotlin+KTX

    Firebase.auth.signOut()

    Java

    FirebaseAuth.getInstance().signOut();