ملفات تعريف الارتباط وتخزين الويب

يستطيع تطبيق الويب أن يحفظ بيانات في متصفحك في ثلاثة أماكن شائعة. ملفات تعريف الارتباط (cookies) أزواج صغيرة من اسم وقيمة يضعها الخادم عبر ترويسة الاستجابة Set-Cookie، ويعيدها المتصفح في ترويسة الطلب Cookie. أما التخزين المحلي (local storage) وتخزين الجلسة (session storage)، ويُسمّيان معاً تخزين الويب (web storage)، فمخزنان للمفاتيح والقيم لا يقرأ منهما ولا يكتب فيهما إلا JavaScript الصفحة، ولا يصل شيء منهما إلى الخادم ما لم يرسله سكربت.

فمتجر إلكتروني مثلاً قد يستخدم الثلاثة معاً: ملف تعريف ارتباط يبقيك مسجّل الدخول، والتخزين المحلي يحفظ سلة التسوق بعد إغلاق المتصفح، وتخزين الجلسة يحفظ خطوة الدفع في علامة تبويب واحدة. لقد رأيتها في لوحة Application في درس أدوات المطوّر في المتصفح؛ وهنا تتعلم القواعد التي تحدد ما يبقى بعد إعادة التحميل وفتح علامة تبويب جديدة وإعادة تشغيل المتصفح وتسجيل الخروج، لتتوقع النتيجة قبل أن تختبرها.

لماذا يهم

العيوب في هذا المجال تدور حول بيانات خاطئة تبقى أو بيانات صحيحة تختفي: مستخدم يظل مسجّل الدخول بعد تسجيل الخروج، وسلة تُفرَّغ عند التحديث، وعملية دفع تتعطل في علامة تبويب ثانية، وعنوان المستخدم السابق ظاهر على جهاز مشترك. ونادراً ما تظهر في تشغيل المسار السعيد (happy path)، لأن أحداً لا يغلق علامة التبويب أو يعيد تشغيل المتصفح أو يبدّل الحساب.

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

ملفات تعريف الارتباط من وجهة نظر المتصفح

تعرّف RFC 6265 طريقة عمل ملفات تعريف الارتباط. يخزّن المتصفح كل ملف يقبله ويرفقه بالطلبات اللاحقة التي تطابق مضيفَه ومسارَه، إلى أن تنتهي صلاحيته. والسمات (attributes) هي التي تحدد إلى أين يذهب الملف وكم يعيش، ولذلك فهي ما تختبره.

في المثال أدناه لا يملك sid تاريخ انتهاء، فهو ملف تعريف ارتباط للجلسة (session cookie)، أما lang فدائم (persistent) لمدة سنة واحدة (⁦31,536,000⁩ ثانية). وفي الطلبات اللاحقة لا يرسل المتصفح إلا أزواج name=value دون السمات، ولهذا تفحص السمات في الاستجابة أو في لوحة Application.

  • Expires (تاريخ) أو Max-Age (عدد ثوانٍ) يجعل الملف دائماً، وإذا وُجدا معاً فالأولوية لـMax-Age. ومن دونهما يكون ملف جلسة يُحذف عند انتهاء جلسة المتصفح.
  • Domain وPath يحددان المضيفين والمسارات التي تستلمه. ومن دون Domain لا يستلمه إلا المضيف نفسه الذي وضعه.
  • Secure يجعله يُرسل عبر HTTPS فقط، وHttpOnly يخفيه عن JavaScript فلا يظهر في document.cookie.
  • SameSite (Strict أو Lax أو None) أحدث من RFC 6265 لكن المتصفحات الحالية تدعمه، وهو يتحكم في إرسال الملف مع الطلبات التي تبدأ من مواقع أخرى. والقيمة None تتطلب Secure؛ وتحقّق من أن التطبيق يضبط القيمة صراحةً بدلاً من الاعتماد على القيمة الافتراضية للمتصفح.
  • تطلب RFC 6265 من المتصفحات دعم ⁦4096⁩ بايت على الأقل لكل ملف و⁦50⁩ ملفاً على الأقل لكل نطاق. وكل طلب مطابق يحمل الملفات معه، فالملفات الكبيرة تضيف بايتات إلى كل طلب، وقد يرفض الخادم طلباً تكبر ترويساته أكثر من اللازم.
HTTP/1.1 200 OK
Set-Cookie: sid=Yx81Tq0bA2; Path=/; Secure; HttpOnly; SameSite=Lax
Set-Cookie: lang=ar; Max-Age=31536000; Path=/; Secure; SameSite=Lax

GET /account HTTP/1.1
Host: shop.example
Cookie: sid=Yx81Tq0bA2; lang=ar

جرّبها: محلّل ملفات تعريف الارتباط (Cookies) جرّبها: محلّل ترويسات HTTP

كيف يحافظ المتصفح على الجلسة

بروتوكول HTTP عديم الحالة (stateless): كل طلب قائم بذاته. ولكي يبقيك مسجّل الدخول، ينشئ الخادم جلسة عند تسجيل الدخول ويحفظ بياناتها لديه، ولا يعطي المتصفح إلا معرّف جلسة عشوائياً، يكون عادةً في ملف تعريف ارتباط مثل sid. يرسله المتصفح مع كل طلب، فيبحث الخادم عن الجلسة. فالمتصفح يحمل المفتاح لا الجلسة.

وبعض تطبيقات الصفحة الواحدة (single-page applications) تستلم بدلاً من ذلك رمزاً، غالباً JWT (اختصار JSON Web Token: سلسلة نصية موقّعة تحمل معلومات مثل هوية المستخدم ووقت انتهاء الصلاحية)، وتحفظه في تخزين الويب أو في الذاكرة، ثم تضيفه بنفسها إلى ترويسة Authorization. وبما أن أي سكربت في الصفحة يستطيع قراءة تخزين الويب، تنصح OWASP بعدم حفظ معرّفات الجلسات أو الرموز فيه، بينما ملف تعريف الارتباط ذو HttpOnly بعيد عن متناول السكربتات. وفي الحالتين تنطبق فحوص الجلسة نفسها:

  • تسجيل الدخول: يتغيّر معرّف الجلسة عند تسجيل الدخول، فلا تنفع بعده قيمة زُرعت قبله.
  • تسجيل الخروج: يُبطل الخادم الجلسة. وحذف ملف تعريف الارتباط لا يكفي؛ فالطلب المعاد إرساله بالمعرّف القديم يجب أن يُرفض.
  • مهلات الجلسة (timeouts): بعد مدة الخمول والمدة المطلقة المتفق عليهما تنتهي الجلسة، ويصل المستخدم إلى صفحة تسجيل الدخول برسالة واضحة لا إلى خطأ.
  • علامات التبويب: ملف تعريف ارتباط الجلسة مشترك بين كل علامات التبويب، فبعد تسجيل الخروج في إحداها يجب أن تعرض البقية صفحة تسجيل الدخول عند أول إجراء تالٍ.

جرّبها: مفكّك ترميز JWT

التخزين المحلي وتخزين الجلسة

يعرّف معيار HTML تخزين الويب. يحفظ المخزنان مفاتيح وقيماً نصية فقط، ويُفصلان حسب الأصل (origin)، أي البروتوكول والمضيف والمنفذ، فنسختا الموقع على http وhttps لا تتشاركان البيانات، ولا يُستخدمان إلا من JavaScript، مثل localStorage.setItem('cart', value). ولا يُرسل شيء منهما إلى الخادم تلقائياً. ويختلفان في مدة البقاء وفي المشاركة:

  • التخزين المحلي مشترك بين كل علامات التبويب للأصل نفسه وليس له تاريخ انتهاء: يبقى بعد إعادة تشغيل المتصفح حتى يمسحه سكربت أو المستخدم أو المتصفح.
  • تخزين الجلسة خاص بعلامة تبويب واحدة. يبقى بعد إعادة التحميل ويُمسح عند إغلاقها؛ وعلامة التبويب الجديدة تحصل على مخزن خاص بها، أما تكرار علامة التبويب فينسخه.
  • القيم نصوص فقط. والكائن الذي يُحفظ دون تحويله أولاً إلى نص بـJSON.stringify يصبح [object Object]، وهو عيب تستطيع رؤيته في لوحة Application.
  • المساحة لكل أصل محدودة، وهي عادةً بضعة ميغابايتات بحسب المتصفح. وعندما تمتلئ يُطلق setItem خطأً يجب أن يعالجه التطبيق.
  • النافذة الخاصة تحصل على تخزين منفصل يتخلص منه المتصفح عند انتهاء الجلسة الخاصة.
يذهب ملف تعريف الارتباط إلى الخادم مع كل طلب مطابق ويمكن إخفاؤه عن السكربتات بـHttpOnly؛ ويبقى التخزين المحلي في المتصفح عبر علامات التبويب وإعادة التشغيل، وتخزين الجلسة في علامة تبويب واحدة حتى تُغلق.

ملف تعريف الارتباطRFC 6265

يضعه الخادم عبر Set-Cookie، أو يضعه سكربت ما لم يكن HttpOnly

يُرسل إلى الخادم:
تلقائياً مع كل طلب مطابق
مشترك بين:
كل علامات التبويب في المتصفح
مدة البقاء:
حتى Expires أو Max-Age؛ ومن دونهما حتى تنتهي جلسة المتصفح
تقرؤه سكربتات الصفحة:
لا، إذا كان HttpOnly
أول فحص:
بعد تسجيل الخروج يرفض الخادم قيمة الجلسة القديمة

التخزين المحليlocalStorage

مفاتيح وقيم نصية يكتبها JavaScript الصفحة

يُرسل إلى الخادم:
لا يُرسل تلقائياً أبداً
مشترك بين:
كل علامات التبويب للأصل نفسه
مدة البقاء:
بلا انتهاء: حتى يمسحه سكربت أو المستخدم أو المتصفح
تقرؤه سكربتات الصفحة:
دائماً
أول فحص:
لا يرى المستخدم B أبداً ما تركه المستخدم A

تخزين الجلسةsessionStorage

الواجهة نفسها، لكن لعلامة تبويب واحدة

يُرسل إلى الخادم:
لا يُرسل تلقائياً أبداً
مشترك بين:
علامة تبويب واحدة فقط؛ وتكرارها ينسخه
مدة البقاء:
حتى تُغلق علامة التبويب؛ ويبقى بعد إعادة التحميل
تقرؤه سكربتات الصفحة:
دائماً
أول فحص:
علامة التبويب الجديدة تبدأ دون خطوة العلامة القديمة

مدد البقاء المعروضة هي الافتراضية: فالمتصفح الذي يستعيد الجلسة السابقة عند بدء التشغيل يستعيد معها ملفات الجلسة وتخزين الجلسة أيضاً.

مثال تطبيقي: تسجيل الدخول والسلة والدفع

يضع متجرٌ sid ملفَّ تعريف ارتباط للجلسة، ويضع remember ملفاً لمدة ⁦30⁩ يوماً فقط عندما يختار المستخدم «تذكرني»، ويحفظ السلة في التخزين المحلي وخطوة الدفع في تخزين الجلسة. قبل الاختبار يتوقع المختبِر كل نتيجة من القواعد السابقة، ثم يتحقق منها في الصفحة وفي لوحة Application. ويفترض الجدول أن المستخدم اختار «تذكرني»؛ وكل خلية نتيجة متوقعة، وكل صف يصبح حالة اختبار.

إعادة فتح المتصفح دون «تذكرني» تعني تسجيل الدخول من جديد مع بقاء السلة؛ ومع «تذكرني» يُصدر المتجر sid جديداً بواسطة remember، الذي يجب أن يتوقف عن العمل بعد ⁦30⁩ يوماً. أما بقاء السلة بعد تسجيل الخروج فقرار يخص المنتج لا المختبِر: اطرحه سؤالاً وسجّل الإجابة في حالة الاختبار.

Action                         sid       remember   cart (local)   step (session)
Reload the page                kept      kept       kept           kept
Open the shop in a new tab     sent      sent       same cart      empty
Close and reopen the browser   gone (1)  kept       kept           gone (1)
Log out                        deleted   deleted    decide (2)     gone
Clear site data                gone      gone       gone           gone

Old sid replayed after logout  -> login page or 401, never account data
Shop after clearing site data  -> loads logged out, empty cart, no error
(1) unless the browser restores the previous session on start-up
(2) product decision: on a shared computer the next user sees the cart

اختبار بقاء البيانات والمسح والخصوصية

استخدم حساب اختبار، ولا تعدّل البيانات المحفوظة أو تمسحها إلا في أنظمة مسموح لك باختبارها. وغطِّ المجالات الخمسة التالية؛ وتبيّن أسطر Console التي تلي القائمة كيف تقرأ تخزين الويب وتغيّره مباشرةً.

  • بقاء البيانات (persistence): إعادة التحميل، وعلامة تبويب جديدة، وعلامة تبويب مكررة، وإعادة تشغيل المتصفح، ولحظة انتهاء الصلاحية، مع مقارنة كل نتيجة بالمتطلب.
  • المسح: احذف ملف تعريف ارتباط واحداً أو مفتاحاً واحداً، ثم امسح كل بيانات الموقع. يجب أن يبدأ التطبيق بداية نظيفة دون صفحة فارغة أو خطأ في السكربت.
  • القيم القديمة والتالفة: القيمة غير الصالحة أو ذات الصيغة القديمة، كما يحدث بعد إصدار غيّر الصيغة، يجب ألا تعطّل المستخدمين العائدين.
  • الجهاز المشترك: سجّل الدخول بالمستخدم A، ثم اخرج، ثم ادخل بالمستخدم B. لا يجوز أن يظهر لـB شيء من بيانات A، لا الاسم ولا العنوان ولا السلة ولا عمليات البحث ولا المسودات.
  • الخصوصية: كلمات المرور وأرقام البطاقات لا مكان لها في ملفات تعريف الارتباط أو تخزين الويب، وسائر البيانات الشخصية لا تُحفظ إلا ما دامت ميزةٌ ما تحتاجها، وملفات الجلسة تحمل Secure وHttpOnly. وإذا طلب الموقع الموافقة على ملفات تعريف الارتباط، فارفض غير الضرورية منها وتأكد في لوحة Application أن شيئاً منها لم يُضبط.
localStorage.getItem('cart')          // read what the shop stored
localStorage.setItem('cart', '{bad')  // invalid JSON: reload, does the page recover?
sessionStorage.clear()                // empty this tab's session storage
document.cookie                       // HttpOnly cookies are not listed

أخطاء شائعة

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

من منظور ضمان الجودة

اسأل مبكراً عمّا تحفظه الميزة في المتصفح، وأين، وإلى متى. فجدول قصير فيه الاسم والآلية والسمات ومدة البقاء وما يفعله تسجيل الخروج هو أساس اختبار يستطيع المطورون ومالك المنتج ومراجعو الأمان التحقق منه. والنقاط المفتوحة مثل «هل تبقى السلة بعد تسجيل الخروج؟» أو «كم تدوم ميزة تذكرني؟» أسئلة عن المتطلبات، لا قرارات للمختبِر.

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

تمرين عملي

توقّع ما تحفظه بوابة الموظفين ثم تحقّق منه

المطلوب

بعد تسجيل الدخول تضبط بوابة للموظفين ملفات تعريف الارتباط والتخزين المبيَّنة أدناه. لكل عنصر، حدّد كم يعيش وهل هو مشترك بين علامات التبويب. ثم اكتب العيوب أو المخاطر التي ستبلغ عنها، وثلاث حالات اختبار تؤكد أن تسجيل الخروج يعمل.

المادة
Response to POST /login
Set-Cookie: session=Qm91c2E1Vq; Path=/
Set-Cookie: theme=dark; Max-Age=86400; Path=/; SameSite=Lax

localStorage on https://portal.example
  user    {"name":"Sara Ali","email":"sara@example.com","token":"eyJhbGciOi..."}
  draft   "Notes for the Sunday meeting"

sessionStorage on https://portal.example
  wizard  {"step":3}
الخطوات
  1. صنّف كل عنصر: ملف تعريف ارتباط أو تخزين محلي أو تخزين جلسة، ودائم أو للجلسة.
  2. توقّع ما يبقى بعد فتح علامة تبويب جديدة وإعادة تشغيل المتصفح وتسجيل الخروج.
  3. افحص سمات كل ملف تعريف ارتباط ومحتوى كل مفتاح تخزين بحثاً عن مشكلات أمان وخصوصية.
  4. اكتب حالات اختبار تسجيل الخروج مع نتائجها المتوقعة.
إظهار الإجابة النموذجية
الإجابة النموذجية

session بلا Expires أو Max-Age، فهو ملف جلسة مشترك بين كل علامات التبويب ويُحذف عند انتهاء جلسة المتصفح. وtheme دائم: يعيش ⁦86,400⁩ ثانية (يوماً واحداً)، وتشترك فيه كل علامات التبويب، ويبقى بعد إعادة التشغيل خلال ذلك اليوم. وuser وdraft يبقيان في التخزين المحلي عبر علامات التبويب وإعادة التشغيل حتى يُمسحا. أما wizard فلا يوجد إلا في علامة التبويب التي أنشأته: تبدأ علامة التبويب الجديدة من دونه، ويختفي بإغلاق علامته.

العيوب والمخاطر: يفتقد session السمتين Secure وHttpOnly، فقد ينتقل عبر HTTP غير المشفّر ويستطيع أي سكربت قراءته، كما أنه بلا SameSite صريحة. والمفتاح user يحفظ رمزاً يستطيع أي سكربت قراءته، وتبقى بيانات المستخدم الشخصية على الجهاز بعد إغلاق المتصفح. ولصق سطري Set-Cookie في أداة «محلّل ملفات تعريف الارتباط» (Cookie Analyzer) يعرض السمات الناقصة.

سؤال لمالك المنتج: هل يُفترض أن يبقى draft بعد تسجيل الخروج؟ فعلى جهاز مشترك سيرى المستخدم التالي ملاحظات سارة.

اختبارات تسجيل الخروج: أولاً، بعد تسجيل الخروج يُحذف session ويُزال user من التخزين المحلي. ثانياً، الطلب المعاد إرساله بقيمة session القديمة، أو بالرمز القديم، يعيد صفحة تسجيل الدخول أو 401 Unauthorized، لا بيانات البوابة. ثالثاً، بعد تسجيل الخروج في علامة تبويب، تعرض العلامة الأخرى صفحة تسجيل الدخول عند أول إجراء تالٍ.

أسئلة شائعة

هل ملف تعريف ارتباط الجلسة هو نفسه تخزين الجلسة؟

لا. ملف تعريف ارتباط الجلسة ملف بلا Expires أو Max-Age: يرسله المتصفح إلى الخادم، ويشاركه بين كل علامات التبويب، ويحذفه عند انتهاء جلسة المتصفح. أما تخزين الجلسة فمخزن JavaScript خاص بعلامة تبويب واحدة، لا يُرسل إلى الخادم تلقائياً ويُمسح عند إغلاق تلك العلامة.

لماذا بقي ملف تعريف ارتباط الجلسة بعد أن أعدت تشغيل المتصفح؟

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

أين يجب أن يحفظ التطبيق رمز تسجيل الدخول؟

هذا قرار تصميم يعود إلى المطورين ومراجعي الأمان، لكن على المختبِر أن يعرف المفاضلة: ملف تعريف الارتباط ذو HttpOnly وSecure لا تقرؤه السكربتات، بينما يستطيع أي سكربت قراءة ما في تخزين الويب. وفي المقابل يُرسل ملف تعريف الارتباط تلقائياً، فيحتاج إلى حماية من تزوير الطلبات عبر المواقع (CSRF): رموز مكافحة CSRF، مع SameSite طبقةً إضافية. وأياً كان الاختيار، اختبر أن بيانات الاعتماد تُمسح عند تسجيل الخروج ويرفضها الخادم بعد ذلك.

المراجع

  • RFC 6265: HTTP State Management MechanismIETF · 2011كيف تُضبط ملفات تعريف الارتباط وتُرسَل: سمات Set-Cookie (Expires وMax-Age وDomain وPath وSecure وHttpOnly) وقواعد نطاقها.
  • HTML Living StandardWHATWGلغة HTML، بما فيها مراجع المحارف المسماة (الكيانات) وقواعد التحليل التي تتبعها المتصفحات.
  • OWASP Cheat Sheet Series: Session ManagementOWASP Foundationإنتروبيا معرّف الجلسة وسمات ملف تعريف الارتباط والانتهاء والتجديد بعد تسجيل الدخول ومعالجة تسجيل الخروج.
  • Using HTTP cookies — MDNMDN Web Docs (Mozilla)عمر ملف تعريف الارتباط ونطاقه وسمات Secure/HttpOnly/SameSite وآثارها الأمنية.

تدرّب بأدوات متقن

تحليل ترويسات طلبات واستجابات HTTP الخام إلى جدول منظّم مع الشروحات.

الويب

فكّ ترميز ترويسة JWT وحمولته، وفحص المطالبات (claims) والتحقق من انتهاء الصلاحية — بالكامل داخل متصفحك.

الأمان

للتعمّق أكثر

تقدّمك في هذا الموضوع

جزء من مسار تعلم ضمان الجودة