شرح 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

لكن غالباً لا نعدل القيم مباشرة داخل هذا الملف، بل نضع القيم الخاصة بالبيئة داخل:

.env

SESSION_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مشاريع بسيطة أو بيئة تطوير
arrayTesting
cookieحالات خاصة تتطلب Client-side encrypted storage

استخدام Database Session Driver

إذا لم يكن لديك جدول Sessions يمكنك إنشاء Migration باستخدام:

php artisan make:session-table

ثم:

php artisan migrate

وبعدها داخل:

.env

استخدم:

SESSION_DRIVER=database

SESSION_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
Path

SESSION_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 أداة قوية ومنظمة لإدارة الحالة المؤقتة للمستخدم دون تحويلها إلى بديل عن قاعدة البيانات أو استخدامها لحفظ معلومات لا يفترض أن تكون مؤقتة.