تكامل Firebase

مصادقة Firebase — تسجيل الدخول بـ Google وحالة المصادقة

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

مصادقة Firebase — تسجيل الدخول بـ Google وحالة المصادقة

توفر مصادقة Firebase حلاً متكاملاً للخلفية لإدارة هوية المستخدم. في هذا الدرس ستتعلم كيفية دمج تسجيل الدخول بـ Google كمزود OAuth، وكيفية استخدام authStateChanges() للحفاظ على حالة المصادقة عبر إعادة تشغيل التطبيق، وحماية المسارات بتحويل المستخدمين غير المصادقين إلى شاشة تسجيل الدخول. هذه المهارات الثلاث معاً تشكل أساس كل تطبيق Flutter آمن.

المتطلبات والحزم

قبل كتابة أي كود تأكد من أن pubspec.yaml يتضمن التبعيات التالية:

dependencies:
  firebase_core: ^2.27.0
  firebase_auth: ^4.19.0
  google_sign_in: ^6.2.1

نفذ flutter pub get، ثم تأكد من وضع google-services.json (Android) وGoogleService-Info.plist (iOS) في مجلدات المنصة الصحيحة. على Android يجب أيضاً تفعيل طريقة تسجيل الدخول بـ Google في لوحة تحكم Firebase تحت Authentication → Sign-in method.

ملاحظة: يتطلب iOS إضافةً إدخال CFBundleURLTypes في Info.plist يحتوي على معرف العميل المعكوس من GoogleService-Info.plist. إغفاله يجعل نافذة تسجيل الدخول تفتح وتغلق فوراً دون أي رسالة خطأ.

ربط تسجيل الدخول بـ Google

تسير عملية تسجيل الدخول في خطوتين: الحصول على بيانات اعتماد GoogleSignInAccount من حزمة google_sign_in، ثم تبادلها للحصول على UserCredential من Firebase عبر signInWithCredential(). غلف كل شيء في try/catch لأن أخطاء الشبكة والإلغاء وشهادات SHA غير الصحيحة تظهر هنا.

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

class AuthService {
  final FirebaseAuth _auth = FirebaseAuth.instance;
  final GoogleSignIn _googleSignIn = GoogleSignIn();

  /// يفتح شاشة موافقة Google OAuth ويسجل المستخدم دخوله في Firebase.
  /// يُعيد [UserCredential] عند النجاح.
  Future<UserCredential?> signInWithGoogle() async {
    try {
      // الخطوة 1 — فتح منتقي حساب Google
      final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
      if (googleUser == null) return null; // المستخدم ألغى العملية

      // الخطوة 2 — الحصول على رموز المصادقة من الحساب المختار
      final GoogleSignInAuthentication googleAuth =
          await googleUser.authentication;

      // الخطوة 3 — بناء بيانات اعتماد Firebase من تلك الرموز
      final OAuthCredential credential = GoogleAuthProvider.credential(
        accessToken: googleAuth.accessToken,
        idToken: googleAuth.idToken,
      );

      // الخطوة 4 — تسجيل الدخول في Firebase والحصول على UserCredential
      return await _auth.signInWithCredential(credential);
    } catch (e) {
      debugPrint('فشل تسجيل الدخول بـ Google: $e');
      return null;
    }
  }

  Future<void> signOut() async {
    await Future.wait([
      _auth.signOut(),
      _googleSignIn.signOut(),
    ]);
  }
}
نصيحة: استدعِ دائماً كلاً من FirebaseAuth.signOut() وGoogleSignIn.signOut() عند تسجيل الخروج. إذا أجريت تسجيل الخروج من Firebase فقط، سيختار منتقي حساب Google الحساب السابق تلقائياً في محاولة تسجيل الدخول التالية متجاوزاً شاشة اختيار الحساب.

الحفاظ على حالة المصادقة مع authStateChanges()

تُعيد FirebaseAuth.instance.authStateChanges() تدفقاً Stream<User?> يُصدر قيمة جديدة كلما سجّل المستخدم دخوله أو خروجه أو جُدِّد الرمز. يُطلق التدفق أيضاً مرة واحدة فور الاشتراك — وهذا الأمر حيوي لأن Firebase يخزن المستخدم الأخير على الجهاز، لذا إذا كان المستخدم قد سجّل دخوله مسبقاً يُصدر التدفق User غير فارغة حتى قبل اكتمال أي اتصال بالشبكة. وهذه هي الطريقة التي تتجنب بها إجبار المستخدمين على تسجيل الدخول في كل مرة يبدأ التطبيق من الصفر.

import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';

class AuthGate extends StatelessWidget {
  const AuthGate({super.key});

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: FirebaseAuth.instance.authStateChanges(),
      builder: (context, snapshot) {
        // لا يزال في انتظار أول إصدار
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Scaffold(
            body: Center(child: CircularProgressIndicator()),
          );
        }

        // المستخدم مسجل دخوله — انتقل إلى التطبيق الرئيسي
        if (snapshot.hasData) {
          return const HomeScreen();
        }

        // لا يوجد مستخدم — اعرض شاشة تسجيل الدخول
        return const LoginScreen();
      },
    );
  }
}

ضع AuthGate كـ home في MaterialApp الخاص بك. يتولى التدفق جميع الانتقالات — تسجيل الدخول والخروج وانتهاء صلاحية الرمز — دون أي منطق تنقل يدوي في معالجات تسجيل الدخول أو الخروج.

حماية المسارات وتحويل المستخدمين غير المصادقين

في التطبيقات التي تستخدم مسارات مسماة أو حزمة router، يكون النمط مشابهاً: اقرأ FirebaseAuth.instance.currentUser بشكل متزامن داخل callback الـ redirect، أو استمع إلى التدفق داخل ChangeNotifier أو مزود Riverpod. يُغطي نهج AuthGate أعلاه بالفعل الحماية على مستوى التنقل للتطبيقات البسيطة.

تحذير: يمكن أن تكون FirebaseAuth.instance.currentUser غير فارغة لجزء من الثانية حتى بعد اكتمال signOut() لأن Firebase يحدّث الكاش بشكل غير متزامن. اعتمد دائماً على تدفق authStateChanges() للتنقل التفاعلي بدلاً من قراءة currentUser مباشرة في دالة build.

تجميع كل شيء معاً

اربط الخدمة بشاشة تسجيل دخول تستدعي AuthService().signInWithGoogle() عند الضغط على الزر. نظراً لأن AuthGate يستمع إلى التدفق، فإن تسجيل الدخول الناجح ينقل المستخدم تلقائياً إلى HomeScreen — دون الحاجة إلى Navigator.push().

النقطة الرئيسية: ترتكز مصادقة Firebase في Flutter على ثلاثة محاور: (1) signInWithCredential() لتبادل رمز OAuth بجلسة Firebase، (2) authStateChanges() لقيادة التنقل بشكل تفاعلي، و(3) تسجيل الخروج من كلٍّ من Firebase ومزود OAuth لمسح الجلسة بالكامل. تمنح هذه المحاور الثلاثة تطبيقك مصادقة آمنة ومستمرة بأقل قدر من الكود النمطي.