أساسيات OAuth 2.0 وتسجيل الدخول بحساب Google
أساسيات OAuth 2.0 وتسجيل الدخول بحساب Google
OAuth 2.0 هو إطار التفويض المعياري في الصناعة الذي يتيح لتطبيق طرف ثالث الحصول على وصول محدود إلى حساب المستخدم على خدمة أخرى — دون أن يرى كلمة مرور المستخدم قط. في Flutter، يُشكّل أساس تدفقات تسجيل الدخول الاجتماعي مثل Google Sign-In وGitHub وFacebook وApple. فهم البروتوكول أمر ضروري قبل ربط أي حزمة SDK.
المفاهيم الأساسية لـ OAuth 2.0
يحدد OAuth 2.0 أربعة أدوار وتدفقَي منح رئيسيين ذوَي صلة بتطبيقات الهاتف المحمول:
- مالك المورد — المستخدم النهائي الذي يمتلك البيانات (مثل ملفه الشخصي على Google).
- العميل — تطبيق Flutter الخاص بك الذي يطلب الوصول.
- خادم التفويض — الخادم الذي يصادق المستخدم ويُصدر الرموز المميزة (مثل
accounts.google.com). - خادم الموارد — واجهة API التي تحتفظ ببيانات المستخدم (مثل
www.googleapis.com).
تدفق رمز التفويض (الموصى به للهاتف المحمول)
يُعدّ تدفق رمز التفويض أكثر أنواع المنح أماناً لتطبيقات الهاتف المحمول والجانب الخادم. يستخدم رمز تفويض قصير العمر يُستبدل بالرموز المميزة على الخلفية، مما يبقي الرموز بعيدة عن حركة مرور الشبكة على الجهاز. الخطوات هي:
- يُعيد التطبيق توجيه المستخدم إلى صفحة تسجيل الدخول الخاصة بخادم التفويض.
- يصادق المستخدم ويوافق على النطاقات المطلوبة.
- يُعيد خادم التفويض التوجيه إلى تطبيقك مع رمز تفويض يُستخدم مرة واحدة.
- يستبدل التطبيق (أو خلفيته) الرمز بـ رمز وصول ورمز تحديث عبر طلب POST يتضمن سر العميل.
- يُستخدم رمز الوصول لاستدعاء خادم الموارد نيابةً عن المستخدم.
google_sign_in مع ذلك تلقائياً على Android وiOS.التدفق الضمني (قديم — تجنّبه)
صُمِّم التدفق الضمني في الأصل لتطبيقات الويب أحادية الصفحة. يتخطى خطوة تبادل الرمز ويُعيد رمز الوصول مباشرةً في جزء URI إعادة التوجيه. مشاكله على الهاتف المحمول تشمل:
- يظهر رمز الوصول في عنوان URL وسجلات الجهاز.
- لا يُصدر رمز تحديث، لذا تكون الجلسات قصيرة العمر.
- عرضة لتسريب الرموز عبر ترويسات المُحيل أو محفوظات المتصفح.
تُهمِّش الممارسة الحالية الأفضل لأمان OAuth 2.0 صراحةً التدفق الضمني. استخدم دائماً تدفق رمز التفويض مع PKCE لتطبيقات Flutter.
تنفيذ Google Sign-In مع حزمة google_sign_in
تُغلّف حزمة google_sign_in منصة Google Identity الأصيلة على كلٍّ من Android (خدمات Google Play) وiOS (SDK GoogleSignIn). أضفها إلى مشروعك:
pubspec.yaml — إضافة الاعتماديات
dependencies:
flutter:
sdk: flutter
google_sign_in: ^6.2.1
firebase_core: ^2.27.0
firebase_auth: ^4.19.0
على Android، سجّل تطبيقك في Firebase Console، نزّل ملف google-services.json، وضعه في android/app/. على iOS، نزّل ملف GoogleService-Info.plist، أضفه إلى مشروع Xcode، وسجّل معرف العميل المعكوس كنظام URL. هذه خطوات إعداد لمرة واحدة؛ تتولى الحزمة بعد ذلك معالجة تدفق OAuth نيابةً عنك.
خدمة Google Sign-In — التنفيذ الكامل
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
class GoogleAuthService {
final GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: [
'email',
'profile',
// أضف نطاقات إضافية هنا إذا لزم، مثل 'https://www.googleapis.com/auth/drive.readonly'
],
);
final FirebaseAuth _auth = FirebaseAuth.instance;
/// يبدأ تدفق Google Sign-In ويربط بيانات الاعتماد بـ Firebase Auth.
/// يُعيد [User] الذي سجّل الدخول عند النجاح، أو null إذا ألغى المستخدم.
Future<User?> signInWithGoogle() async {
try {
// الخطوة 1: عرض منتقي حساب Google (تدفق رمز التفويض عبر PKCE)
final GoogleSignInAccount? googleUser = await _googleSignIn.signIn();
if (googleUser == null) {
// ألغى المستخدم حوار تسجيل الدخول
return null;
}
// الخطوة 2: استرداد رموز OAuth المميزة
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
// الخطوة 3: التبادل للحصول على بيانات اعتماد Firebase
final OAuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
// الخطوة 4: تسجيل الدخول إلى Firebase ببيانات اعتماد Google
final UserCredential userCredential =
await _auth.signInWithCredential(credential);
return userCredential.user;
} catch (e) {
// معالجة الأخطاء: فشل الشبكة، إلغاء تسجيل الدخول، تعطيل الحساب، إلخ.
rethrow;
}
}
/// تسجيل الخروج من كلٍّ من Firebase وحساب Google.
Future<void> signOut() async {
await Future.wait([
_auth.signOut(),
_googleSignIn.signOut(),
]);
}
}
الاستماع إلى تغييرات حالة المصادقة في شجرة الودجات
بعد تسجيل الدخول بنجاح، تبثّ Firebase Auth تدفقاً من أحداث User?. اشترك في FirebaseAuth.instance.authStateChanges() في الودجت الجذر للتنقل التفاعلي للمستخدم إلى الشاشة الصحيحة:
Auth Gate — التنقل التفاعلي
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 SignInScreen();
},
);
}
}
اعتبارات الأمان
- لا تطلب نطاقات لا تستخدمها. النطاقات الواسعة للغاية علامة تحذيرية لخصوصية المستخدم وستفشل في التحقق من OAuth لدى Google للتطبيقات المنشورة.
- تحقق من صحة رمز المعرف على جانب الخادم إذا كنت تحتفظ بخلفيتك الخاصة. تفعل Firebase Auth ذلك تلقائياً عند استدعاء
signInWithCredential. - خزّن الرموز المميزة بأمان. تحتفظ Firebase Auth بالجلسة في وحدة التخزين الآمنة للمنصة (Keychain على iOS، وEncryptedSharedPreferences على Android). لا تُخزّن رموز الوصول الخام بنفسك.
- تعامل مع انتهاء صلاحية الرمز. تنتهي صلاحية رمز الوصول عادةً في ساعة واحدة. يمكن لحزمة
google_sign_inتحديثه صامتاً عبرgoogleUser.authenticationإذا كان رمز تحديث صالح موجوداً.
_googleSignIn.signInSilently() عند بدء تشغيل التطبيق لاستعادة جلسة سابقة دون عرض منتقي الحسابات. يمنح ذلك المستخدمين العائدين تجربة سلسة.ملخص
يفصل OAuth 2.0 بين المصادقة (من أنت) والتفويض (ما يُسمح لك بفعله). تدفق رمز التفويض مع PKCE هو الاختيار الصحيح لتطبيقات الهاتف المحمول؛ أما التدفق الضمني فمهمَّل وغير آمن. تُنفّذ حزمة google_sign_in هذا التدفق بشكل أصيل، ويُنشئ FirebaseAuth.signInWithCredential جسراً بين بيانات اعتماد Google الناتجة ونظام إدارة مستخدمي Firebase. يمنح الاشتراك في authStateChanges() تطبيقَك رؤية تفاعلية ومتسقة دائماً للمستخدم الحالي.