المصادقة والأمان

تسجيل الدخول الاجتماعي: Apple وFacebook وGitHub

16 دقيقة الدرس 4 من 12

تسجيل الدخول الاجتماعي: Apple وFacebook وGitHub

إضافة موفري تسجيل الدخول الاجتماعي تحسّن معدلات التحويل بشكل ملحوظ من خلال السماح للمستخدمين بالمصادقة باستخدام حسابات يثقون بها بالفعل. يغطي هذا الدرس دمج تسجيل الدخول عبر Apple (إلزامي لتطبيقات iOS التي تقدم أي تسجيل دخول من طرف ثالث)، وتسجيل الدخول عبر Facebook، وGitHub OAuth في مصادقة Firebase. لكل موفر متطلبات تهيئة خاصة بمنصته، لكنها جميعاً تتبع نفس النمط العام: الحصول على بيانات اعتماد OAuth من SDK الموفر، ثم تمريرها إلى Firebase لإنشاء حساب أو ربطه.

ملاحظة: تسجيل الدخول عبر Apple مطلوب بموجب إرشادات مراجعة متجر App Store (القاعدة 4.8) لأي تطبيق iOS يقدم تسجيل دخول من طرف ثالث أو اجتماعي. تقديم تطبيق iOS بتسجيل دخول عبر Facebook أو Google دون تسجيل الدخول عبر Apple سيؤدي إلى رفضه.

تسجيل الدخول عبر Apple

يستخدم تسجيل الدخول عبر Apple حزمة sign_in_with_apple على Flutter إلى جانب تدفق بيانات اعتماد firebase_auth. يتضمن الإعداد تغييرات في تهيئة Xcode وتغييرات في Firebase Console.

إعداد iOS / Xcode:

  • في Xcode، افتح Signing & Capabilities وأضف ميزة Sign In with Apple.
  • في Firebase Console، فعّل موفر Apple ضمن Authentication → Sign-in method وأدخل Services ID وTeam ID وKey ID والمفتاح الخاص (ملف .p8) من Apple Developer.
  • سجّل Services ID في حساب Apple Developer الخاص بك وهيّئ Return URL ليشير إلى عنوان URL لإعادة توجيه OAuth في Firebase كما هو موضح في وحدة التحكم.

إعداد Android (اختياري لكن موصى به): يمر تسجيل الدخول عبر Apple على Android من خلال تدفق ويب. أضف URI إعادة التوجيه إلى Apple Services ID الخاص بك، وسجّل بصمة SHA-1 لتطبيق Android في Firebase.

تسجيل الدخول عبر Apple — تطبيق Flutter

import 'package:firebase_auth/firebase_auth.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
import 'dart:convert';
import 'dart:math';

// توليد nonce آمن تشفيرياً
String _generateNonce([int length = 32]) {
  const charset =
      '0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
  final random = Random.secure();
  return List.generate(length, (_) => charset[random.nextInt(charset.length)])
      .join();
}

// تجزئة SHA-256 للـ nonce (استيراد حزمة crypto)
// String _sha256ofString(String input) { ... }

Future<UserCredential?> signInWithApple() async {
  final rawNonce = _generateNonce();
  // final nonce = _sha256ofString(rawNonce); // تجزئة قبل الإرسال إلى Apple

  try {
    final appleCredential = await SignInWithApple.getAppleIDCredential(
      scopes: [
        AppleIDAuthorizationScopes.email,
        AppleIDAuthorizationScopes.fullName,
      ],
      // nonce: nonce, // تمرير الـ nonce المجزأ إلى Apple
    );

    final oauthCredential = OAuthProvider('apple.com').credential(
      idToken: appleCredential.identityToken,
      rawNonce: rawNonce, // تمرير الـ nonce الخام إلى Firebase
    );

    return await FirebaseAuth.instance.signInWithCredential(oauthCredential);
  } on SignInWithAppleAuthorizationException catch (e) {
    if (e.code == AuthorizationErrorCode.canceled) {
      return null; // أغلق المستخدم النافذة
    }
    rethrow;
  }
}
تحذير: لا يُعيد Apple اسم المستخدم وبريده الإلكتروني إلا في أول تسجيل دخول فقط. احفظ appleCredential.givenName وappleCredential.familyName فور أول تسجيل دخول — عمليات المصادقة اللاحقة ستُعيد null لهذه الحقول.

تسجيل الدخول عبر Facebook

يستخدم تسجيل الدخول عبر Facebook الحزمة الرسمية flutter_facebook_auth. يجب إنشاء تطبيق Facebook في بوابة Meta for Developers وإدراج معرف الحزمة (iOS) واسم الحزمة + تجزئة المفتاح (Android) في القائمة البيضاء.

خطوات الإعداد:

  • أضف تطبيقك إلى بوابة Meta للمطورين، واحصل على App ID وApp Secret.
  • في Firebase Console، فعّل موفر Facebook وأدخل App ID وApp Secret.
  • أضف facebook_app_id وfacebook_client_token إلى AndroidManifest.xml وInfo.plist كما تقتضي وثائق الحزمة.
  • لنظام Android، أنشئ تجزئة مفتاح الإصدار باستخدام keytool وأضفها إلى لوحة تحكم تطبيق Facebook ضمن Android → Key Hashes.

تسجيل الدخول عبر Facebook — تطبيق Flutter

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';

Future<UserCredential?> signInWithFacebook() async {
  // بدء تدفق تسجيل الدخول عبر Facebook
  final LoginResult loginResult = await FacebookAuth.instance.login(
    permissions: ['email', 'public_profile'],
  );

  if (loginResult.status == LoginStatus.cancelled) {
    return null;
  }

  if (loginResult.status != LoginStatus.success) {
    throw Exception('فشل تسجيل الدخول عبر Facebook: ${loginResult.message}');
  }

  // الحصول على رمز الوصول
  final AccessToken accessToken = loginResult.accessToken!;

  // إنشاء بيانات اعتماد Facebook لـ Firebase
  final OAuthCredential facebookCredential =
      FacebookAuthProvider.credential(accessToken.tokenString);

  // تسجيل الدخول إلى Firebase ببيانات اعتماد Facebook
  return FirebaseAuth.instance.signInWithCredential(facebookCredential);
}

// جلب بيانات الملف الشخصي (اختياري)
Future<Map<String, dynamic>> getFacebookUserData() async {
  final userData = await FacebookAuth.instance.getUserData(
    fields: 'name,email,picture.width(200)',
  );
  return userData;
}

GitHub OAuth

لا تمتلك GitHub حزمة Flutter مخصصة. بدلاً من ذلك، تستخدم GithubAuthProvider من Firebase مع تدفق OAuth عبر الويب، عادةً عبر signInWithPopup على الويب أو تدفق إعادة توجيه عبر رابط عميق على الهاتف المحمول.

خطوات الإعداد:

  • سجّل OAuth App في إعدادات GitHub → Developer settings. اضبط Authorization Callback URL على عنوان URL لإعادة توجيه OAuth في Firebase.
  • فعّل موفر GitHub في Firebase Console (Authentication → Sign-in method) وأدخل Client ID وClient Secret من GitHub.
  • على الهاتف المحمول، استخدم firebase_dynamic_links أو مخطط URL مخصص للتعامل مع إعادة التوجيه إلى التطبيق بعد تفويض GitHub.

تسجيل الدخول عبر GitHub — باستخدام signInWithProvider (Flutter Web والهاتف المحمول)

import 'package:firebase_auth/firebase_auth.dart';

Future<UserCredential?> signInWithGitHub() async {
  final githubProvider = GithubAuthProvider();

  // طلب نطاقات إضافية عند الحاجة
  githubProvider.addScope('read:user');
  githubProvider.addScope('user:email');

  try {
    // على الويب: يفتح نافذة منبثقة؛ على الهاتف: يعيد التوجيه عبر المتصفح
    final UserCredential credential =
        await FirebaseAuth.instance.signInWithProvider(githubProvider);
    return credential;
  } on FirebaseAuthException catch (e) {
    if (e.code == 'account-exists-with-different-credential') {
      // التعامل مع ربط بيانات الاعتماد (انظر القسم أدناه)
      await _linkGitHubToExistingAccount(e);
      return null;
    }
    rethrow;
  }
}

Future<void> _linkGitHubToExistingAccount(FirebaseAuthException e) async {
  // جلب الموفرين المسجلين لهذا البريد الإلكتروني
  final email = e.email!;
  final methods = await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
  // طلب من المستخدم تسجيل الدخول بالموفر الموجود، ثم الربط
  debugPrint('سجّل الدخول أولاً باستخدام: $methods، ثم اربط GitHub.');
}

ربط بيانات الاعتماد

قد يحاول مستخدم سبق له تسجيل الدخول بالبريد الإلكتروني وكلمة المرور تسجيل الدخول مرة أخرى عبر موفر اجتماعي يشترك في نفس البريد الإلكتروني. يُلقي Firebase خطأ account-exists-with-different-credential. الحل الصحيح هو:

  • تسجيل دخول المستخدم باستخدام موفره الأصلي.
  • استدعاء user.linkWithCredential(pendingCredential) لإرفاق الموفر الاجتماعي الجديد بالحساب الموجود.
  • من تلك النقطة، يمكن للمستخدم تسجيل الدخول بأي من الطريقتين.
نصيحة: فعّل ربط الحسابات في Firebase Console (Authentication → Settings → User account linking) لـ "منع إنشاء حسابات متعددة بنفس عنوان البريد الإلكتروني." بهذه الطريقة يُطلق Firebase خطأ الربط بنفسه بدلاً من إنشاء حساب مكرر بصمت.

ملخص

في هذا الدرس تعلمت كيفية دمج ثلاثة موفري تسجيل دخول اجتماعي في تطبيق Flutter باستخدام مصادقة Firebase:

  • تسجيل الدخول عبر Apple — إلزامي لتطبيقات iOS في App Store التي تقدم تسجيل دخول من طرف ثالث؛ يستخدم nonce آمناً، ويتطلب ميزة Xcode + تهيئة Firebase + Apple Developer Services ID.
  • تسجيل الدخول عبر Facebook — يستخدم حزمة flutter_facebook_auth؛ يتطلب تطبيق Meta Developer ومعرفات الحزمة وتجزئات المفاتيح وبيانات الاعتماد في Firebase Console.
  • GitHub OAuth — لا توجد SDK مخصصة؛ استخدم GithubAuthProvider مع signInWithProvider؛ يتطلب GitHub OAuth App وعنوان URL لـ callback مهيأ في Firebase.

تتبع الموفرات الثلاثة جميعها نفس نمط تسليم بيانات الاعتماد: الحصول على رمز الموفر، ولفّه في OAuthCredential من Firebase، ثم استدعاء signInWithCredential. تعامل مع خطأ account-exists-with-different-credential في كل مكان يُستخدم فيه تسجيل الدخول الاجتماعي لتجنب الحسابات المكررة.