في هذا الدرس 6تصفّح المواضيع
لمصادقة المستخدم، اجلب السجل المطابق للبريد الموحد عبر استعلام PDO محضر، ومرر التجزئة المخزنة إلى الدالة password_verify($inputPassword, $user["password_hash"]).
- لا تضع كلمة المرور أبداً داخل شرط
WHEREفي SQL؛ بل اجلب السجل بالبريد أولاً ثم تحقق من التجزئة في PHP. - تستخرج دالة
password_verify()الملح وخوارزمية التجزئة من النص المخزن تلقائياً. - افحص
password_needs_rehash()بعد نجاح الدخول لترقية معامل التكلفة القديم تلقائياً.
ترجع password_verify() القيمة true عندما تتطابق كلمة المرور المدخلة مع التجزئة المخزنة.
<?php
$storedUser = [
'id' => 42,
'email' => '[email protected]',
'password_hash' => password_hash('Correct#Pass9', PASSWORD_DEFAULT),
];
$inputPass = 'Correct#Pass9';
if ($storedUser && password_verify($inputPass, $storedUser['password_hash'])) {
echo "Authenticated User ID: {$storedUser['id']}\n";
}
إذا أظهر نموذج الدخول رسالة "البريد غير موجود" منفصلة عن "كلمة المرور خاطئة"، سيتمكن المهاجم من اكتشاف عناوين البريد المسجلة؛ لذا أرجع دائماً رسالة خطأ موحدة للحالتين.
- استخدم نفس الرسالة الموحدة:
"Invalid email or password."سواء لم يوجد البريد أو فشلت دالةpassword_verify(). - نفّذ فحص
password_verify()وهمياً عند عدم وجود البريد لتوحيد زمن الاستجابة ومنع هجمات التوقيت. - قيّد عدد محاولات الدخول الفاشلة المتتالية لنفس العنوان أو البريد.
حتى عندما لا يوجد السجل، فإن استدعاء password_verify() مع تجزئة ثابتة يمنع كشف وجود الحساب عبر فرق زمن الاستجابة.
<?php
$dummyHash = '$2y$10$usesomesillystringfore7hnbRJHxXVLeakoG8K30oukPsA.ztMG';
$userRow = false; // Email not found in DB
$inputPass = 'guess123';
$hashToCheck = $userRow ? $userRow['password_hash'] : $dummyHash;
$isValid = password_verify($inputPass, $hashToCheck) && $userRow !== false;
echo $isValid ? "Welcome\n" : "Invalid email or password.\n";
فور التحقق من صحة بيانات الدخول، استدعِ session_regenerate_id(true) قبل تخزين $_SESSION["user_id"] لإبطال أي محاولة لتثبيت الجلسة.
- تمرير المعامل
trueيحذف ملف الجلسة القديم من الخادم ويصدر معرف جلسة جديداً تماماً. - خزّن فقط المعرفات الأساسية مثل
user_idوuser_roleووقت الدخول في$_SESSION. - لا تخزن أبداً تجزئة كلمة المرور داخل مصفوفة
$_SESSION.
تحفظ الجلسة معرف المستخدم الرقمي، وصلاحيته، والطابع الزمني لتسجيل الدخول.
<?php
$_SESSION = [];
$authenticatedId = 42;
$role = 'admin';
$_SESSION['user_id'] = $authenticatedId;
$_SESSION['user_role'] = $role;
$_SESSION['auth_time'] = 1762512000;
echo "Session bound to User #{$authenticatedId} ({$role})\n";
تفحص دالة حارس المصادقة (Auth Guard) وجود isset($_SESSION["user_id"]) في بداية كل صفحة محمية، وتحول الزوار غير المسجلين إلى /login فوراً.
- اجمع الفحص في دالة موحدة قابلة لإعادة الاستخدام مثل
requireAuth(). - استدعِ دائماً
exit;فور إرسال رأس التحويلLocation: /loginحتى لا يُنفذ الكود الخاص أسفله. - في صفحات الإدارة، أضف فحصاً ثانياً يتحقق من الصلاحية
$_SESSION["user_role"] === "admin".
عند غياب user_id يمنع الحارس الوصول، وعند وجوده يسمح للمتحكم بالمتابعة.
<?php
function checkAuthGuard(array $session): string
{
if (empty($session['user_id'])) {
return 'Redirect -> /login (401 Unauthorized)';
}
return 'Access Granted for User #' . $session['user_id'];
}
echo checkAuthGuard([]) . "\n";
echo checkAuthGuard(['user_id' => 42]) . "\n";
لا تخزن أبداً معرف المستخدم أو كلمة مروره في كوكي "تذكرني"؛ بل استخدم توكن مقسوماً إلى selector:validator بحيث يُخزن فقط ناتج تجزئة validator في قاعدة البيانات.
- يُستخدم
selectorللبحث السريع المفهرس عن سجل التوكن في قاعدة البيانات. - تُجزّأ قيمة
validatorالعشوائية السرية بـhash("sha256", $validator)وتقارن عبرhash_equals(). - حتى لو تسرب جدول التوكنات من قاعدة البيانات، لن يتمكن المهاجم من تزوير كوكي الدخول!
يجمع الكوكي بين selector:validator بينما تحفظ قاعدة البيانات فقط تجزئة SHA-256 للمفتاح السري.
<?php
$selector = bin2hex(random_bytes(8));
$validator = bin2hex(random_bytes(16));
$cookieValue = "$selector:$validator";
$dbHashedValidator = hash('sha256', $validator);
[$inSel, $inVal] = explode(':', $cookieValue, 2);
$valid = hash_equals($dbHashedValidator, hash('sha256', $inVal));
echo "Remember-Me Token Verified: " . ($valid ? 'YES' : 'NO') . "\n";
يجب أن يقبل معالج تسجيل الخروج طلبات POST المحمية بتوكن CSRF فقط، ويفرغ $_SESSION، ويلغي كوكي الجلسة في المتصفح، ثم يستدعي session_destroy().
- لماذا نستخدم
POST /logout؟ لأن جعل الخروج رابطGETيسمح لأي وسم صورة خبيث<img src="/logout">بإخراج المستخدم قسرياً! - أفرغ مصفوفة
$_SESSION = []في الذاكرة قبل تدمير ملف الجلسة. - احذف توكن "تذكرني" من قاعدة البيانات ومن كوكيز المتصفح معاً عند الخروج.
تفريغ المصفوفة $_SESSION = [] يزيل جميع بيانات الجلسة فوراً من الذاكرة الحالية.
| عملية المصادقة | الإجراء الأمني الإلزامي | الأهمية الأمنية |
|---|---|---|
| Login Failure | Unified error message + dummy hash check | Prevents user email enumeration |
| Login Success | session_regenerate_id(true) | Prevents session fixation attacks |
| Protected Route | Auth guard + exit; after redirect | Stops unauthorized execution immediately |
| Remember Me Cookie | Split selector:validator (SHA-256 in DB) | Prevents token theft from DB leaks |
| Logout Endpoint | POST /logout + CSRF + clear cookie | Prevents forced cross-site logout & replay |
<?php
$_SESSION = ['user_id' => 42, 'user_role' => 'admin'];
// Step 1: Clear session array in memory
$_SESSION = [];
// Step 2: Verify memory is clean before session_destroy()
echo "Active Session Keys: " . count($_SESSION) . "\n";
echo "Logout complete -> Redirect to /login\n";
ملخص الدرس
حصاد الدرسمراجعة مركّزة لأهم المفاهيم والقواعد الهندسية التي أتقنتها في هذا الدرس قبل التطبيق العملي
ماذا تعلّمت في هذا الدرس؟
يجب أن يقبل معالج تسجيل الخروج طلبات POST المحمية بتوكن CSRF فقط، ويفرغ $_SESSION، ويلغي كوكي الجلسة في المتصفح، ثم يستدعي session_destroy(). لماذا نستخدم POST /logout؟ لأن جعل الخروج رابط GET يسمح لأي وسم صورة خبيث <img src="/logout"> بإخراج المستخدم قسرياً! أفرغ مصفوفة $_SESSION = [] في الذاكرة قبل تدمير ملف الجلسة. احذف توكن "تذكرني" من قاعدة البيانات ومن كوكيز المتصفح معاً عند الخروج.
سجّل الدخول لحفظ تقدمك
تابع إنجاز المواضيع والمصطلحات والمهام، وكمّل من آخر خطوة على أي جهاز.
المصطلحات4
راجع المعنى، اشرحه لنفسك، ثم علّم البطاقة كمفهومة.
- 01 Login & Auth System تسجيل الدخول والمصادقة
- نفّذ نظام تسجيل دخول آمن والتحقق من الهوية وتجديد الجلسة وحراس حماية الصفحات وميزة تذكرني وتسجيل الخروج النظيف في PHP 8.
- للمراجعة
- 02 Fetching User and Verifying Password Hash جلب المستخدم بالبريد والتحقق من التشفير
- المفهوم الأساسي لقسم جلب المستخدم بالبريد والتحقق من التشفير في لغة PHP الحديثة.
- للمراجعة
- 03 Preventing User Enumeration in Errors منع كشف وجود البريد عبر رسائل الخطأ الموحدة
- الصياغة البرمجية والتطبيق العملي المباشر لقسم منع كشف وجود البريد عبر رسائل الخطأ الموحدة.
- للمراجعة
- 04 Starting Authenticated Session Safely بدء جلسة المستخدم وتدوير المعرف فور الدخول
- قواعد التحقق وأفضل الممارسات القياسية الخاصة بقسم بدء جلسة المستخدم وتدوير المعرف فور الدخول.
- للمراجعة
لا توجد بطاقات تطابق البحث والتصفية.
اقرأ البطاقات بحرية. سجّل الدخول لحفظ تقدم مراجعتك.
الاختصارات4
راجع المعنى، اشرحه لنفسك، ثم علّم البطاقة كمفهومة.
- 01 PHP PHP
- PHP: Hypertext Preprocessor
- لغة البرمجة النصية على جانب الخادم لبناء تطبيقات الويب الديناميكية وواجهات البرمجة.
- للمراجعة
- 02 CLI CLI
- Command-Line Interface
- واجهة سطر الأوامر التي تتيح تشغيل ملفات PHP مباشرة عبر الطرفية.
- للمراجعة
- 03 FPM FPM
- FastCGI Process Manager
- مدير عمليات FastCGI عالي الأداء لمعالجة طلبات خادم الويب في PHP.
- للمراجعة
- 04 HTML HTML
- HyperText Markup Language
- لغة الترميز القياسية التي يولدها محرك PHP ويرسلها إلى متصفح العميل بعد التنفيذ.
- للمراجعة
لا توجد بطاقات تطابق البحث والتصفية.
اقرأ البطاقات بحرية. سجّل الدخول لحفظ تقدم مراجعتك.
مهامك قبل الاختبار
خطوة صغيرة كل مرة. وإنجاز تقدر ترجع له.
-
أنشئ ملف PHP محلياً ونفّذ المثال البرمجي المشروح في قسم جلب المستخدم بالبريد والتحقق من التشفير للتحقق من المخرجات.
-
بدّل بين الحالات الثلاث في المحاكي التفاعلي ضمن قسم منع كشف وجود البريد عبر رسائل الخطأ الموحدة وقارن مخرجات التنفيذ.
تأشير المهمة إقرار منك بإتمامها، وليس تصحيحًا آليًا للكود. يُحفظ تقدمك في حسابك.
اختبر فهمك.
ووصلها للإتقان.
كل سؤال خطوة لقدّام. اختار، اكتب كود، وشوف نتيجتك فورًا.
أكمل المهام، واجمع كل الإجابات الصحيحة. أقل من 100%؟ راجع وأعد المحاولة.
الأسئلة متاحة للاطلاع. سجّل الدخول للتفاعل وحفظ تقدمك.
لمحة عن التحدي8 أسئلة تنتظرك +
لماذا يجب أن يرجع تسجيل الدخول الفاشل رسالة موحدة "البريد أو كلمة المرور غير صحيحة" بدلاً من تحديد أيهما الخاطئ؟
الرسائل التفصيلية تكشف للمهاجم وجود الحساب من عدمه.
ما الدالة التي يجب استدعاؤها فور نجاح password_verify() أثناء تسجيل الدخول؟
تدوير معرف الجلسة عند تغير مستوى الصلاحية يمنع هجمات تثبيت الجلسة.
ماذا يحدث إذا نفّذ حارس المصادقة الأمر header("Location: /login"); دون استدعاء exit; بعده مباشرة؟
الدالة header() تضيف رأس HTTP فقط ولا توقف تنفيذ السكريبت تلقائياً!
في نمط توكن تذكرني المقسوم (selector:validator)، كيف يجب حفظ جزء الـ validator في قاعدة البيانات؟
حفظ تجزئة SHA-256 فقط يحمي جلسات المستخدمين حتى لو تسرب جدول التوكنات.
لماذا يجب أن يتم تسجيل الخروج عبر طلب POST محمي بتوكن CSRF بدلاً من رابط GET /logout عادي؟
العمليات التي تغير حالة الجلسة مثل الخروج يجب أن تتم عبر POST مع حماية CSRF.
كيف تفرغ جميع متغيرات الجلسة من الذاكرة أثناء تسجيل الخروج قبل استدعاء session_destroy()؟
إسناد $_SESSION = [] يفرغ جميع مفاتيح الجلسة في الذاكرة فوراً.
لماذا ننفذ password_verify() ضد تجزئة وهمية ثابتة حتى عندما لا يكون البريد الإلكتروني موجوداً في قاعدة البيانات؟
تستغرق Bcrypt نحو 100 مللي ثانية؛ وتخطيها عند غياب البريد يجعل الخادم يرد أسرع بـ 100 مرة مما يكشف غياب الحساب.
متى يجب استدعاء الدالة password_needs_rehash($storedHash, PASSWORD_DEFAULT)؟
لحظة تسجيل الدخول الناجح هي الوحيدة التي يمتلك فيها الخادم كلمة المرور النصية الصحيحة لإعادة تجزئتها بمعيار أحدث.