المصادقة باستخدام Apple على Android

يمكنك السماح للمستخدمين بالمصادقة باستخدام Firebase من خلال معرّف Apple الخاص بهم عن طريق استخدام حزمة تطوير البرامج (SDK) من Firebase لتنفيذ عملية تسجيل الدخول الكاملة باستخدام بروتوكول OAuth 2.0.

قبل البدء

لتسجيل دخول المستخدمين باستخدام Apple، عليك أولاً إعداد ميزة "تسجيل الدخول باستخدام Apple" على موقع مطوّري Apple الإلكتروني، ثم تفعيل Apple كموفّر خدمة تسجيل الدخول لمشروعك على Firebase.

الانضمام إلى برنامج Apple Developer

لا يمكن إعداد ميزة "تسجيل الدخول باستخدام حساب على Apple" إلا من خلال أعضاء برنامج Apple Developer.

ضبط ميزة "تسجيل الدخول باستخدام حساب على Apple"

في موقع مطوّري Apple الإلكتروني، اتّبِع الخطوات التالية:

  1. اربط موقعك الإلكتروني بتطبيقك كما هو موضّح في القسم الأول من مقالة إعداد ميزة "تسجيل الدخول باستخدام Apple" على الويب. عندما يُطلب منك ذلك، سجِّل عنوان URL التالي كعنوان URL للردّ:

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

    يمكنك الحصول على رقم تعريف مشروع Firebase الخاص بك من صفحة إعداداتFirebase وحدة التحكّم.

    عند الانتهاء، سجِّل رقم تعريف الخدمة الجديد الذي ستحتاج إليه في القسم التالي.

  2. إنشاء مفتاح خاص لتطبيق "تسجيل الدخول باستخدام Apple" ستحتاج إلى المفتاح الخاص الجديد ورقم تعريف المفتاح في القسم التالي.
  3. إذا كنت تستخدم أيًا من ميزات Firebase Authentication التي ترسل رسائل إلكترونية إلى المستخدمين، بما في ذلك تسجيل الدخول باستخدام رابط البريد الإلكتروني، وإثبات ملكية عنوان البريد الإلكتروني، وإبطال تغيير الحساب، وغيرها، عليك إعداد خدمة ترحيل البريد الإلكتروني الخاص من Apple وتسجيل noreply@YOUR_FIREBASE_PROJECT_ID.firebaseapp.com (أو نطاق نموذج البريد الإلكتروني المخصّص) لكي تتمكّن Apple من ترحيل الرسائل الإلكترونية التي ترسلها Firebase Authentication إلى عناوين البريد الإلكتروني المجهولة الهوية من Apple.

تفعيل Apple كموفّر خدمة تسجيل الدخول

  1. أضِف Firebase إلى مشروع Android. احرص على تسجيل توقيع SHA-1 لتطبيقك عند إعداد تطبيقك في وحدة تحكّم Firebase.
  2. في وحدة تحكّم Firebase، افتح قسم المصادقة. في علامة التبويب طريقة تسجيل الدخول، فعِّل موفّر Apple. حدِّد معرّف الخدمة الذي أنشأته في القسم السابق. في قسم إعدادات مسار رمز OAuth، حدِّد أيضًا معرّف فريق Apple والمفتاح الخاص ومعرّف المفتاح اللذين أنشأتهما في القسم السابق.

الالتزام بمتطلبات Apple بشأن البيانات المخفية الهوية

يمنح خيار "تسجيل الدخول باستخدام Apple" المستخدمين إمكانية إخفاء هوية بياناتهم، بما في ذلك عنوان بريدهم الإلكتروني، عند تسجيل الدخول. يحصل المستخدمون الذين يختارون هذا الخيار على عناوين بريد إلكتروني تتضمّن النطاق privaterelay.appleid.com. عند استخدام ميزة "تسجيل الدخول باستخدام Apple" في تطبيقك، عليك الالتزام بأي سياسات أو بنود مطوّرين سارية من Apple بشأن معرّفات Apple المجهولة الهوية هذه.

ويشمل ذلك الحصول على أي موافقة مطلوبة من المستخدم قبل ربط أي معلومات شخصية تحدد الهوية مباشرةً بمعرّف Apple مجهول الهوية. عند استخدام "مصادقة Firebase"، قد يشمل ذلك الإجراءات التالية:

  • ربط عنوان بريد إلكتروني بمعرّف Apple مخفيّ الهوية أو العكس
  • ربط رقم هاتف بمعرّف Apple مخفي الهوية أو العكس
  • ربط بيانات اعتماد اجتماعية غير مجهولة الهوية (Facebook أو Google أو غير ذلك) بمعرّف Apple مجهول الهوية أو العكس

يُرجى العِلم أنّ القائمة أعلاه ليست شاملة. يُرجى الرجوع إلى اتفاقية الترخيص الخاصة ببرنامج Apple للمطوّرين في قسم "الاشتراك" ضمن حساب المطوّر للتأكّد من أنّ تطبيقك يستوفي متطلبات Apple.

التعامل مع عملية تسجيل الدخول باستخدام حزمة تطوير البرامج (SDK) من Firebase

على أجهزة Android، أسهل طريقة لمصادقة المستخدمين باستخدام Firebase من خلال حساباتهم على Apple هي التعامل مع عملية تسجيل الدخول بأكملها باستخدام حزمة تطوير البرامج (SDK) لنظام التشغيل Android من Firebase.

للتعامل مع تدفّق تسجيل الدخول باستخدام حزمة تطوير البرامج (SDK) لنظام التشغيل Android من Firebase، اتّبِع الخطوات التالية:

  1. أنشئ مثيلاً من OAuthProvider باستخدام أداة الإنشاء الخاصة به مع معرّف الموفّر apple.com:

    Kotlin

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

    Java

    OAuthProvider.Builder provider = OAuthProvider.newBuilder("apple.com");
    
  2. اختياري: حدِّد نطاقات إضافية لبروتوكول OAuth 2.0 تتجاوز النطاقات التلقائية التي تريد طلبها من موفّر المصادقة.

    Kotlin

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

    Java

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

    عند تفعيل خيار حساب واحد لكل عنوان بريد إلكتروني، يطلب Firebase تلقائيًا نطاقات البريد الإلكتروني والاسم. في حال تغيير هذا الإعداد إلى حسابات متعدّدة لكل عنوان بريد إلكتروني، لن يطلب Firebase أي نطاقات من Apple إلا إذا حدّدتها.

  3. اختياري: إذا كنت تريد عرض شاشة تسجيل الدخول من Apple بلغة أخرى غير الإنجليزية، اضبط المَعلمة locale. يمكنك الاطّلاع على مستندات &quot;تسجيل الدخول باستخدام Apple&quot; لمعرفة اللغات المتوافقة.

    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. يمكنك المصادقة باستخدام Firebase من خلال عنصر موفّر OAuth. يُرجى العِلم أنّه على عكس عمليات FirebaseAuth الأخرى، ستتحكّم هذه العملية في واجهة المستخدم من خلال فتح علامة تبويب مخصّصة في Chrome. وبالتالي، لا تشِر إلى نشاطك في OnSuccessListener وOnFailureListener اللذين ترفقهما لأنّهما سيتم فصلهما على الفور عند بدء العملية لواجهة المستخدم.

    عليك أولاً التحقّق مما إذا كنت قد تلقّيت ردًا. يؤدي تسجيل الدخول بهذه الطريقة إلى وضع النشاط في الخلفية، ما يعني أنّه يمكن للنظام استرداده أثناء عملية تسجيل الدخول. للتأكّد من أنّك لن تطلب من المستخدم إعادة المحاولة في حال حدوث ذلك، عليك التحقّق مما إذا كانت النتيجة معروضة من قبل.

    للتحقّق مما إذا كانت هناك نتيجة معلّقة، اتّصِل بالرقم 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");
    }
    

    إذا لم تكن هناك نتيجة معلّقة، ابدأ عملية تسجيل الدخول من خلال استدعاء 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);
                        }
                    });
    

    على عكس موفّري الخدمات الآخرين الذين تتوافق معهم خدمة Firebase Auth، لا توفّر Apple عنوان URL للصورة.

    عندما يختار المستخدم عدم مشاركة بريده الإلكتروني مع التطبيق، توفّر Apple عنوان بريد إلكتروني فريدًا لهذا المستخدم (بالتنسيق xyz@privaterelay.appleid.com)، وتشاركه مع تطبيقك. وإذا كنت قد أعددت خدمة ترحيل البريد الإلكتروني الخاص، تعيد Apple توجيه الرسائل الإلكترونية المرسَلة إلى العنوان المجهول إلى عنوان البريد الإلكتروني الحقيقي للمستخدم.

    تشارك Apple معلومات المستخدمين، مثل الاسم المعروض، مع التطبيقات فقط عند تسجيل المستخدم الدخول لأول مرة. يخزّن Firebase عادةً الاسم المعروض عند تسجيل دخول المستخدم لأول مرة باستخدام Apple، ويمكنك الحصول عليه باستخدام getCurrentUser().getDisplayName(). ومع ذلك، إذا سبق لك استخدام Apple لتسجيل دخول المستخدم إلى التطبيق بدون استخدام Firebase، لن يقدّم Apple إلى Firebase اسم المستخدم المعروض.

إعادة المصادقة وربط الحساب

يمكن استخدام النمط نفسه مع startActivityForReauthenticateWithProvider() الذي يمكنك استخدامه لاسترداد بيانات اعتماد جديدة للعمليات الحسّاسة التي تتطلّب تسجيل دخول حديث:

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

ويمكنك استخدام linkWithCredential() لربط مقدّمي خدمات هوية مختلفين بالحسابات الحالية.

يُرجى العِلم أنّ Apple تفرض عليك الحصول على موافقة صريحة من المستخدمين قبل ربط حساباتهم على Apple ببيانات أخرى.

على سبيل المثال، لربط حساب على Facebook بحساب Firebase الحالي، استخدِم رمز الدخول الذي حصلت عليه من تسجيل دخول المستخدم إلى 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.
        }
      }
    });

إجراء متقدّم: التعامل مع مسار تسجيل الدخول يدويًا

يمكنك أيضًا المصادقة باستخدام Firebase من خلال حساب Apple عن طريق التعامل مع مسار تسجيل الدخول إما باستخدام حزمة تطوير البرامج (SDK) بلغة JavaScript الخاصة بميزة &quot;تسجيل الدخول باستخدام حساب Apple&quot;، أو إنشاء مسار OAuth يدويًا، أو باستخدام مكتبة OAuth، مثل AppAuth.

  1. لكل طلب تسجيل دخول، أنشئ سلسلة عشوائية، وهي "رقم استخدام لمرة واحدة"، ستستخدمه للتأكّد من أنّ رمز التعريف الذي تحصل عليه قد تم منحه تحديدًا استجابةً لطلب المصادقة الذي أرسله تطبيقك. هذه الخطوة مهمة لمنع هجمات إعادة الإرسال.

    يمكنك إنشاء رقم خاص آمن تشفيريًا على Android باستخدام SecureRandom، كما هو موضّح في المثال التالي:

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

    بعد ذلك، احصل على تجزئة SHA246 للرقم الخاص كقيمة سداسية عشرية:

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

    سترسل تجزئة SHA256 للرقم الخاص مع طلب تسجيل الدخول، وستمرّره Apple بدون تغيير في الرد. تتحقّق Firebase من صحة الرد من خلال تجزئة الرقم الخاص الأصلي ومقارنته بالقيمة التي أرسلتها Apple.

  2. ابدأ مسار تسجيل الدخول في Apple باستخدام مكتبة OAuth أو طريقة أخرى. احرص على تضمين الرقم الخاص المجزّأ كمَعلمة في طلبك.

  3. بعد تلقّي ردّ Apple، احصل على الرمز المميّز للمعرّف من الردّ واستخدِمه مع الرقم الخاص غير المجزأ لإنشاء AuthCredential:

    Kotlin

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

    Java

    AuthCredential credential =  OAuthProvider.newCredentialBuilder("apple.com")
        .setIdTokenWithRawNonce(appleIdToken, rawUnhashedNonce)
        .build();
    
  4. يمكنك المصادقة باستخدام Firebase من خلال بيانات اعتماد 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.
              // ...
            }
          }
        });
    

في حال نجاح طلب signInWithCredential، يمكنك استخدام طريقة getCurrentUser للحصول على بيانات حساب المستخدم.

إبطال الرمز المميز

تشترط Apple أن تتيح التطبيقات التي تتيح إنشاء الحسابات للمستخدمين بدء عملية حذف حساباتهم من داخل التطبيق، كما هو موضّح في إرشادات مراجعة App Store.

بالإضافة إلى ذلك، يجب أن تستخدم التطبيقات التي تتيح ميزة "تسجيل الدخول باستخدام Apple" واجهة برمجة التطبيقات REST الخاصة بهذه الميزة لإبطال رموز المستخدمين المميزة.

لاستيفاء هذا الشرط، اتّبِع الخطوات التالية:

  1. استخدِم طريقة startActivityForSignInWithProvider() لتسجيل الدخول باستخدام Apple والحصول على AuthResult.

  2. احصل على رمز الدخول لموفّر Apple.

    Kotlin

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

    Java

    OAuthCredential oauthCredential = (OAuthCredential) authResult.getCredential();
    String accessToken = oauthCredential.getAccessToken();
    
  3. إبطال الرمز المميّز باستخدام واجهة برمجة التطبيقات revokeAccessToken.

    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. أخيرًا، احذف حساب المستخدم (وجميع البيانات المرتبطة به).

    الخطوات التالية

    بعد تسجيل المستخدم الدخول لأول مرة، يتم إنشاء حساب مستخدم جديد وربطه ببيانات الاعتماد، أي اسم المستخدم وكلمة المرور أو رقم الهاتف أو معلومات مقدّم خدمة المصادقة التي سجّل المستخدم الدخول بها. يتم تخزين هذا الحساب الجديد كجزء من مشروع Firebase، ويمكن استخدامه لتحديد هوية المستخدم على مستوى كل تطبيق في مشروعك، بغض النظر عن طريقة تسجيل الدخول.

    • في تطبيقاتك، يمكنك الحصول على معلومات الملف الشخصي الأساسية للمستخدم من العنصر FirebaseUser. راجِع إدارة المستخدمين.

    • في Firebase Realtime Database وCloud Storage قواعد الأمان، يمكنك الحصول على معرّف المستخدِم الفريد للمستخدِم الذي سجّل الدخول من المتغيّر auth، واستخدامه للتحكّم في البيانات التي يمكن للمستخدِم الوصول إليها.

    يمكنك السماح للمستخدمين بتسجيل الدخول إلى تطبيقك باستخدام عدة موفّري مصادقة من خلال ربط بيانات اعتماد موفّر المصادقة بحساب مستخدم حالي.

    لتسجيل خروج مستخدم، اتّبِع الخطوات التالية: signOut:

    Kotlin

    Firebase.auth.signOut()

    Java

    FirebaseAuth.getInstance().signOut();