شرح Sessions في Laravel: التخزين والقراءة والحذف وFlash Data وحماية الجلسات
تعتبر Sessions من أهم الأدوات المستخدمة في تطبيقات الويب، لأنها تسمح لنا بحفظ بيانات خاصة بالمستخدم بين عدة HTTP Requests.
بشكل افتراضي فإن بروتوكول HTTP لا يحتفظ بحالة المستخدم بين الطلبات، أي أن كل Request يعتبر مستقلاً عن الطلب السابق. وهنا يأتي دور Session.
باستخدام Sessions في Laravel يمكننا مثلاً حفظ:
- معلومات مؤقتة خاصة بالمستخدم.
- اللغة المختارة.
- رسائل نجاح أو خطأ بعد Redirect.
- بيانات خطوات نموذج متعدد الصفحات.
- قيم مؤقتة مثل عداد أو فلتر.
- حالة المستخدم أثناء تسجيل الدخول.
وفي هذا المقال سنتعرف بالتفصيل على كيفية استخدام Sessions في Laravel، وأنواع Session Drivers، وطريقة حفظ وقراءة وحذف البيانات، بالإضافة إلى Flash Data وأفضل ممارسات الأمان.
ما هي Session؟
الـ Session عبارة عن وسيلة لحفظ معلومات تخص مستخدماً معيناً بين عدة Requests.
مثلاً عندما يزور المستخدم:
/profileثم ينتقل إلى:
/settingsيمكن للتطبيق معرفة أن الطلبين يعودان إلى نفس جلسة المستخدم.
بشكل مبسط:
Browser
│
│ Session Cookie
▼
Laravel
│
▼
Session Store
│
├── Database
├── Redis
├── File
└── Cookieالمتصفح يحمل عادة معرفاً مرتبطاً بالجلسة، بينما يتم تخزين بيانات Session في المكان الذي يحدده Session Driver.
أمثلة عملية على استخدام Sessions
يمكن استخدام Session في عدد كبير من السيناريوهات.
تخزين اللغة
session([
'locale' => 'ar'
]);تخزين اختيار مؤقت
session([
'selected_category' => 5
]);إظهار رسالة نجاح بعد Redirect
return redirect()
->route('posts.index')
->with(
'success',
'تم إضافة المقال بنجاح'
);تخزين بيانات خطوات Checkout
session([
'checkout.address' => $address
]);إعدادات Session في Laravel
ملف إعدادات Session يوجد في:
config/session.phpومن خلاله يتم تحديد:
Driver
Lifetime
Encryption
Cookie Name
Domain
Secure Cookie
SameSite
HTTP Only
Connectionلكن غالباً لا نعدل القيم مباشرة داخل هذا الملف، بل نضع القيم الخاصة بالبيئة داخل:
.envSESSION_DRIVER
أحد أهم الإعدادات:
SESSION_DRIVER=databaseوهو الذي يحدد المكان الذي ستُخزن فيه بيانات Session.
أنواع Session Drivers في Laravel
Laravel توفر عدة Drivers يمكن اختيار الأنسب منها حسب طبيعة التطبيق.
Database
SESSION_DRIVER=databaseيتم تخزين بيانات Sessions داخل جدول في قاعدة البيانات.
هذه الطريقة مناسبة لكثير من التطبيقات، وتتيح التعامل مع جلسات المستخدمين من خلال قاعدة بيانات مركزية.
File
SESSION_DRIVER=fileيتم تخزين Session داخل ملفات في:
storage/framework/sessionsوهي طريقة بسيطة ومناسبة لبعض البيئات، لكنها ليست مثالية عادة عندما يعمل التطبيق خلف عدة Application Servers.
Redis
SESSION_DRIVER=redisيتم تخزين Sessions في Redis.
Redis سريع جداً ويعتبر خياراً ممتازاً للتطبيقات التي تحتاج إلى Store مركزي سريع أو تعمل خلف Load Balancer.
Memcached
SESSION_DRIVER=memcachedيستخدم Memcached لتخزين بيانات الجلسات.
Cookie
SESSION_DRIVER=cookieفي هذا Driver يتم تخزين بيانات Session داخل Cookies مشفرة وآمنة من خلال Laravel بدلاً من تخزينها في Server-side Store مستقل.
Array
SESSION_DRIVER=arrayيتم الاحتفاظ بالبيانات في PHP Array ولا يتم الاحتفاظ بها بين Requests.
لهذا السبب يستخدم هذا Driver بشكل أساسي في الاختبارات.
DynamoDB
Laravel تدعم أيضاً استخدام AWS DynamoDB لتخزين Sessions.
أي Session Driver أختار؟
| Driver | الاستخدام الشائع |
|---|---|
| database | تطبيقات Laravel التقليدية والمتوسطة |
| redis | التطبيقات عالية الأداء أو متعددة Servers |
| file | مشاريع بسيطة أو بيئة تطوير |
| array | Testing |
| cookie | حالات خاصة تتطلب Client-side encrypted storage |
استخدام Database Session Driver
إذا لم يكن لديك جدول Sessions يمكنك إنشاء Migration باستخدام:
php artisan make:session-tableثم:
php artisan migrateوبعدها داخل:
.envاستخدم:
SESSION_DRIVER=databaseSESSION_LIFETIME
يمكن تحديد مدة Session من:
SESSION_LIFETIME=120القيمة هنا بالدقائق.
أي:
120 Minutes = 2 Hoursوهي المدة التي يمكن للجلسة أن تبقى خلالها قبل انتهاء صلاحيتها حسب إعدادات Session.
انتهاء Session عند إغلاق المتصفح
يمكن التحكم في ذلك من إعداد:
expire_on_closeوالقيمة المرتبطة به في إعدادات التطبيق.
عند تفعيل هذا الخيار، يصبح Session Cookie مرتبطاً بإغلاق Session الخاصة بالمتصفح.
كيفية تخزين البيانات في Session
هناك أكثر من طريقة للتعامل مع Session في Laravel.
أشهر طريقتين هما:
session() helper
$request->session()التخزين باستخدام session() Helper
session([
'name' => 'Ahmed'
]);يمكن أيضاً تخزين أكثر من قيمة:
session([
'name' => 'Ahmed',
'language' => 'ar',
'theme' => 'dark',
]);التخزين باستخدام Request
public function store(Request $request)
{
$request->session()->put(
'language',
'ar'
);
}كما يمكن تمرير Array:
$request->session()->put([
'language' => 'ar',
'theme' => 'dark',
]);استخدام Dot Notation
Laravel تدعم Dot Notation للتعامل مع البيانات المتداخلة.
مثلاً:
$request->session()->put(
'user.settings.language',
'ar'
);ثم يمكن قراءتها باستخدام:
$request->session()->get(
'user.settings.language'
);إضافة عنصر إلى Array داخل Session
يمكن استخدام:
push()مثلاً:
$request->session()->push(
'cart.products',
15
);ثم:
$request->session()->push(
'cart.products',
20
);ستصبح القيمة تقريباً:
[
15,
20
]قراءة البيانات من Session
يمكن استخدام:
$request->session()->get('name');أو:
session('name');تحديد قيمة افتراضية
إذا كانت Session غير موجودة يمكن تحديد قيمة Default.
$language = session(
'language',
'en'
);أو:
$language = $request
->session()
->get(
'language',
'en'
);إذا لم توجد:
languageسيتم إرجاع:
enقراءة جميع بيانات Session
$data = $request
->session()
->all();هذه الطريقة مفيدة أثناء Debugging، لكن لا يفضل عرض جميع بيانات Session للمستخدم في Production.
الحصول على جزء من Session
يمكن الحصول على Keys محددة باستخدام:
$data = $request
->session()
->only([
'name',
'email',
]);أو الحصول على جميع القيم باستثناء مجموعة معينة:
$data = $request
->session()
->except([
'token',
'temporary',
]);التحقق من وجود قيمة باستخدام has()
يمكن استخدام:
if (
$request
->session()
->has('name')
) {
// Session exists
}دالة:
has()ترجع true إذا كان المفتاح موجوداً وكانت قيمته ليست null.
الفرق بين has() وexists()
هذا فرق مهم.
إذا كانت لدينا:
session([
'name' => null
]);فإن:
$request->session()->has('name')ستعتبر القيمة غير موجودة من ناحية has() لأنها تساوي null.
أما:
$request->session()->exists('name')فتتحقق فقط من وجود Key حتى لو كانت قيمته:
nullالتحقق من عدم وجود Session
يمكن استخدام:
if (
$request
->session()
->missing('name')
) {
// Session does not exist
}وهي أوضح من كتابة:
if (
!$request
->session()
->has('name')
)في بعض الحالات.
قراءة Session ثم حذفها مباشرة باستخدام pull()
أحياناً نريد الحصول على قيمة ثم حذفها في نفس الوقت.
بدلاً من:
$value = $request
->session()
->get('coupon');
$request
->session()
->forget('coupon');يمكن كتابة:
$value = $request
->session()
->pull('coupon');ويمكن أيضاً تحديد Default:
$value = $request
->session()
->pull(
'coupon',
null
);زيادة قيمة Session
إذا كانت القيمة Integer:
$request
->session()
->increment('count');مثلاً إذا كانت:
count = 10ستصبح:
count = 11زيادة Session بمقدار معين
$request
->session()
->increment(
'count',
5
);إنقاص Session
$request
->session()
->decrement('count');أو:
$request
->session()
->decrement(
'count',
2
);ما هي Flash Data؟
Flash Data هي بيانات Session مؤقتة نحتاج إليها لفترة قصيرة جداً، عادة Request واحد فقط بعد Redirect.
أشهر مثال:
تم حفظ البيانات بنجاحبعد تنفيذ Create أو Update.
تخزين Flash Data
$request
->session()
->flash(
'success',
'تمت العملية بنجاح'
);يمكن بعد ذلك قراءتها من الصفحة التالية:
{{ session('success') }}Flash Data باستخدام Redirect
الطريقة الأكثر استخداماً:
return redirect()
->route('posts.index')
->with(
'success',
'تم إضافة المقال بنجاح'
);Laravel ستضع هذه الرسالة في Flash Session بشكل تلقائي.
عرض Flash Message في Blade
@if (session('success'))
<div class="alert alert-success">
{{ session('success') }}
</div>
@endifاستخدام now()
إذا أردنا أن تكون Flash Data متاحة للـ Request الحالي فقط:
$request
->session()
->now(
'status',
'تم تنفيذ العملية'
);استخدام reflash()
إذا أردنا الاحتفاظ بجميع Flash Data لطلب إضافي:
$request
->session()
->reflash();مثلاً:
Request 1
↓
Flash Data
↓
Request 2
↓
reflash()
↓
Request 3استخدام keep()
إذا أردنا الاحتفاظ بعناصر محددة فقط:
$request
->session()
->keep([
'success',
'warning',
]);حذف قيمة من Session
لحذف قيمة واحدة:
$request
->session()
->forget('name');حذف عدة قيم
$request
->session()
->forget([
'name',
'email',
'language',
]);حذف جميع بيانات Session
$request
->session()
->flush();يجب استخدام:
flush()بحذر لأنها تقوم بإزالة جميع البيانات المخزنة في Session.
إعادة توليد Session ID
من أهم الجوانب الأمنية في Sessions حماية التطبيق من هجمات:
Session Fixationويمكن إعادة توليد معرف Session باستخدام:
$request
->session()
->regenerate();عند نجاح عملية Authentication يجب أن يتغير Session ID حتى لا يستطيع مهاجم الاستفادة من معرف جلسة تم إنشاؤه قبل تسجيل الدخول.
Laravel تقوم بذلك تلقائياً عند استخدام أنظمة Authentication الرسمية مثل Starter Kits أو Fortify.
ما هي Session Fixation؟
Session Fixation عبارة عن محاولة لجعل الضحية تستخدم Session ID يعرفه المهاجم مسبقاً.
مثلاً بشكل مبسط:
Attacker
│
▼
Known Session ID
│
▼
Victim
│
▼
Login
│
▼
Same Session ID
│
▼
Attacker abuses sessionولهذا يتم تغيير Session ID بعد Authentication.
استخدام invalidate()
إذا أردنا حذف بيانات Session وإعادة توليد Session ID معاً:
$request
->session()
->invalidate();وهذه دالة مهمة جداً خصوصاً عند تسجيل الخروج.
تسجيل الخروج بالطريقة الآمنة
يمكن أن يكون Logout Flow:
public function logout(
Request $request
) {
Auth::logout();
$request
->session()
->invalidate();
$request
->session()
->regenerateToken();
return redirect('/');
}لاحظ أننا قمنا بثلاث خطوات:
Auth::logout()
Session invalidate()
CSRF Token regenerationوهذه طريقة أفضل من الاكتفاء بحذف متغير معين من Session.
ما هي regenerateToken()؟
Laravel تستخدم CSRF Token لحماية Requests الحساسة.
بعد إنهاء الجلسة يمكن إنشاء CSRF Token جديد باستخدام:
$request
->session()
->regenerateToken();Session Cookie Security
هناك عدة إعدادات أمنية مهمة متعلقة بالـ Session Cookie.
من أهمها:
Secure
HttpOnly
SameSite
Domain
PathSESSION_SECURE_COOKIE
على Production الذي يعمل عبر HTTPS يفضل أن تكون Cookie آمنة:
SESSION_SECURE_COOKIE=trueهذا يعني أن المتصفح لن يرسل Session Cookie عبر اتصال HTTP غير مشفر.
HttpOnly
HttpOnly يمنع JavaScript داخل الصفحة من قراءة Session Cookie مباشرة.
هذا يوفر طبقة حماية مهمة ضد بعض سيناريوهات سرقة Cookies من خلال JavaScript.
لكن يجب الانتباه إلى أن HttpOnly لا يغني عن حماية التطبيق من XSS.
SameSite
SameSite تتحكم في كيفية إرسال Cookies مع Requests القادمة من مواقع أخرى.
القيم الشائعة تشمل:
lax
strict
noneويجب اختيارها بناءً على بنية التطبيق وطريقة Authentication والتكاملات الخارجية.
هل يجب تخزين معلومات حساسة في Session؟
يجب التعامل مع Session كبيانات أمنية مهمة، لكن لا يعني ذلك أنه يجب تخزين كل شيء فيها.
من الأفضل عدم تخزين:
Passwords
Credit Card Data
Private API Keys
Permanent Secretsداخل Session.
خزن فقط البيانات التي يحتاجها التطبيق بشكل مؤقت وضروري.
Session ليست Database بديلة
من الأخطاء الشائعة استخدام Session كمكان دائم لحفظ البيانات.
إذا كانت البيانات يجب أن تبقى لفترات طويلة أو يجب أن تكون قابلة للبحث والتقارير والمزامنة، فمن الأفضل تخزينها في Database.
Session مناسبة أكثر إلى:
Temporary User Stateوليس:
Permanent Business Dataمثال: تخزين اللغة
public function changeLanguage(
Request $request
) {
$request->validate([
'language' => [
'required',
'in:ar,en',
],
]);
$request
->session()
->put(
'language',
$request->language
);
return back();
}ثم يمكن قراءتها:
$language = session(
'language',
'ar'
);مثال: عداد بسيط باستخدام Session
public function visit(
Request $request
) {
if (
!$request
->session()
->has('visits')
) {
$request
->session()
->put(
'visits',
0
);
}
$request
->session()
->increment('visits');
return $request
->session()
->get('visits');
}مثال: Shopping Cart بسيط
يمكن تخزين Cart مؤقت في Session:
$request
->session()
->push(
'cart.items',
[
'product_id' => 10,
'quantity' => 2,
]
);ثم قراءة Cart:
$cart = $request
->session()
->get(
'cart.items',
[]
);لكن في المتاجر الكبيرة أو عند الحاجة إلى Cart مشتركة بين عدة أجهزة، قد تكون قاعدة البيانات أكثر ملاءمة.
Sessions مع التطبيقات متعددة Servers
لنفترض أن لدينا:
Load Balancer
│
┌──────┴──────┐
▼ ▼
Server A Server Bإذا كان:
SESSION_DRIVER=fileفقد توجد Session على Server A فقط.
ثم يذهب Request التالي إلى Server B ولا يجد نفس البيانات.
لهذا نستخدم Store مركزي مثل:
Database
أو
Redisفتصبح البنية:
Server A ─┐
│
▼
Redis
▲
│
Server B ─┘Session Blocking
في بعض التطبيقات قد يرسل نفس المستخدم Requests متزامنة تحاول جميعها تعديل Session في الوقت نفسه.
وهذا قد يؤدي في بعض السيناريوهات إلى Race Conditions.
Laravel توفر ميزة:
Session Blockingتسمح بقفل Session لفترة قصيرة حتى ينتهي Request الحالي قبل أن يقوم Request آخر بتعديل نفس Session.
هذه ميزة متقدمة ولا يحتاج إليها كل تطبيق، لكنها مهمة في السيناريوهات التي يحدث فيها تعديل متزامن لنفس Session.
Session Cache
في Laravel الحديثة توجد أيضاً ميزة Session Cache، وهي Cache مرتبطة بجلسة مستخدم معين وليست Global Cache مشتركة مع جميع المستخدمين.
يمكن استخدامها للبيانات المؤقتة الخاصة بالمستخدم والتي تحتاج أن تعيش خلال Session.
مثلاً:
$request
->session()
->cache()
->put(
'calculation',
$result,
now()->addMinutes(10)
);وهي مناسبة مثلاً لتخزين:
- نتائج حسابات مؤقتة.
- استجابات API خاصة بالمستخدم.
- بيانات Forms مؤقتة.
اختبار Sessions في Laravel
Laravel توفر Assertions لاختبار Session.
مثلاً:
$response = $this->post(
'/posts',
[
'title' => 'Laravel',
]
);
$response
->assertSessionHas(
'success'
);ويمكن أيضاً تجهيز Session قبل Request:
$response = $this
->withSession([
'language' => 'ar',
])
->get('/dashboard');أخطاء شائعة عند استخدام Sessions
كتابة config/sessions.php
المسار الصحيح:
config/session.phpاستخدام مسار خاطئ للـ File Sessions
المسار الصحيح:
storage/framework/sessionsاعتبار SESSION_LIFETIME بالثواني
القيمة تحسب بالدقائق.
مثلاً:
SESSION_LIFETIME=120تعني تقريباً:
120 minutesالقول إن has() ترجع null
هذا غير دقيق.
دالة:
has()ترجع Boolean:
true
أو
falseعدم التمييز بين has وexists
has()يتطلب أن تكون القيمة غير null.
بينما:
exists()يتحقق من وجود Key حتى لو كانت قيمته null.
استخدام File Driver مع عدة Servers
استخدم Store مشتركاً مثل Redis أو Database إذا كان التطبيق موزعاً على عدة Servers.
عدم تغيير Session ID بعد Login
استخدم Authentication الرسمي أو:
$request
->session()
->regenerate();عدم إلغاء Session بشكل صحيح عند Logout
يفضل:
Auth::logout();
$request
->session()
->invalidate();
$request
->session()
->regenerateToken();تخزين بيانات حساسة جداً
Session ليست Vault لحفظ Secrets دائمة.
تخزين كمية ضخمة من البيانات
لا تستخدم Session لتخزين Collections كبيرة أو ملفات أو Objects ضخمة دون حاجة.
مثال شامل
public function sessionExample(
Request $request
) {
/*
|--------------------------------------------------------------------------
| Store
|--------------------------------------------------------------------------
*/
$request
->session()
->put(
'user.language',
'ar'
);
/*
|--------------------------------------------------------------------------
| Read
|--------------------------------------------------------------------------
*/
$language = $request
->session()
->get(
'user.language',
'en'
);
/*
|--------------------------------------------------------------------------
| Check
|--------------------------------------------------------------------------
*/
if (
$request
->session()
->has(
'user.language'
)
) {
// Exists and is not null
}
/*
|--------------------------------------------------------------------------
| Flash
|--------------------------------------------------------------------------
*/
$request
->session()
->flash(
'success',
'تم تنفيذ العملية بنجاح'
);
/*
|--------------------------------------------------------------------------
| Increment
|--------------------------------------------------------------------------
*/
$request
->session()
->increment(
'visits'
);
/*
|--------------------------------------------------------------------------
| Delete
|--------------------------------------------------------------------------
*/
$request
->session()
->forget(
'temporary'
);
return response()->json([
'language' => $language,
]);
}ملخص أهم دوال Session في Laravel
| الدالة | الاستخدام |
|---|---|
put() | حفظ قيمة |
get() | قراءة قيمة |
all() | قراءة جميع القيم |
only() | قراءة مجموعة محددة |
except() | قراءة البيانات باستثناء Keys محددة |
has() | التحقق من وجود قيمة غير null |
exists() | التحقق من وجود Key حتى لو كان null |
missing() | التحقق من عدم وجود Key |
push() | إضافة عنصر إلى Array |
pull() | قراءة القيمة ثم حذفها |
increment() | زيادة قيمة رقمية |
decrement() | إنقاص قيمة رقمية |
flash() | تخزين قيمة مؤقتة للطلب التالي |
now() | Flash للطلب الحالي |
reflash() | الاحتفاظ بجميع Flash Data لطلب إضافي |
keep() | الاحتفاظ بـ Flash Keys محددة |
forget() | حذف قيمة أو مجموعة قيم |
flush() | حذف جميع بيانات Session |
regenerate() | إنشاء Session ID جديد |
invalidate() | حذف Session وإنشاء ID جديد |
regenerateToken() | توليد CSRF Token جديد |
الخلاصة
Sessions في Laravel توفر طريقة موحدة وسهلة لحفظ بيانات المستخدم المؤقتة بين HTTP Requests.
ويمكن تلخيص دورة Session بالشكل التالي:
Browser
│
▼
Request
│
▼
Laravel Session
│
├── Store Data
├── Read Data
├── Flash Data
└── Delete Data
│
▼
Session Driver
│
├── Database
├── Redis
├── File
└── Other Driversيمكن استخدام:
session()أو:
$request->session()للتعامل مع Session، وتوفر Laravel دوال جاهزة مثل:
put
get
has
exists
missing
push
pull
increment
decrement
flash
reflash
keep
forget
flush
regenerate
invalidateأما من ناحية الأمان، فمن أهم الممارسات تغيير Session ID بعد Authentication، وإلغاء Session بشكل صحيح عند Logout، واستخدام HTTPS وSecure Cookies، وعدم تخزين بيانات حساسة أو دائمة داخل Session دون حاجة.
وعندما يعمل التطبيق على أكثر من Server فمن الأفضل استخدام Session Store مركزي مثل Database أو Redis بدلاً من File Driver.
بهذه الطريقة تصبح Sessions أداة قوية ومنظمة لإدارة الحالة المؤقتة للمستخدم دون تحويلها إلى بديل عن قاعدة البيانات أو استخدامها لحفظ معلومات لا يفترض أن تكون مؤقتة.
good ?