الـ Logout والـ Refresh والتخزين
الـ JWT ميزته إن السيرفر مش فاكر عنه حاجة، وده بالظبط اللي بيخلي الـ logout صعب. الدرس ده بيحل المشكلة بالـ Access والـ Refresh tokens، وبيجاوب على سؤال بيتسأل في كل إنترفيو: تخزن الـ token فين؟ وفي الآخر المقارنة الكاملة: Session ولا JWT.
- تشرح ليه الـ JWT صعب يتلغي، وتعرف 4 حلول وتمن كل واحد
- تفهم Access و Refresh tokens، و Rotation، و Reuse Detection
- تقارن localStorage و Memory و HttpOnly Cookie قدام XSS و CSRF
- تختار بين Session و Opaque Token و JWT وإنت فاهم التمن
1. مشكلة الـ Logout (Token Revocation)
المستخدم دوس “تسجيل خروج”. مع الـ Session، بتمسح الصف وخلاص. لكن مع الـ JWT، الـ token لسه سليم وتوقيعه صح وصالح لحد الـ exp. لو حد كان سرقه، هيفضل يستخدمه.
نفس المشكلة لما تعمل ban لمستخدم، أو تشيل صلاحية الأدمن من حد، أو المستخدم يغيّر الباسورد عشان شاك إن حسابه اتسرق.
افتكر المحاكي اللي في درس الـ Tokens: الـ Opaque وقع فوراً، والـ JWT فضل شغال لحد الـ exp. الحلول:
| الحل | الفكرة | التمن |
|---|---|---|
| عمر قصير | الـ access token يعيش 5 لـ 15 دقيقة بس | محتاج refresh token (تحت) |
| Denylist | تخزّن الـ jti بتاع الـ tokens الملغية في Redis لحد ما الـ exp بتاعها يعدّي |
بقيت بتسأل store في كل request، يعني رجعت stateful |
| Token Version | عمود token_version في users، وبيتحط في الـ token. لما تزوّده، كل الـ tokens القديمة تبطل |
lookup للمستخدم في كل request |
| تاريخ آخر logout | ترفض أي token الـ iat بتاعه أقدم من آخر “خروج من كل الأجهزة” |
نفس التمن |
الـ JWT ميزته إنه stateless، وعشان تلغيه لازم ترجّع حتة state. وعشان كده العمر القصير هو خط الدفاع الأول.
2. Access Token و Refresh Token
فكّر في تذكرة دخول يومية في معرض، واشتراك سنوي متشال في درج. كل يوم بتطلّع تذكرة جديدة بالاشتراك، والتذكرة هي اللي بتوريها لكل بوابة. لو التذكرة اتسرقت، هتبوظ آخر اليوم. والاشتراك نفسه مش بيخرج من الدرج غير عند شباك واحد.
دوس ▶ شغّل، أو امشي خطوة خطوة بالأسهم.
Refresh Token Rotation و Reuse Detection
كل مرة تستخدم refresh token، بتاخد واحد جديد والقديم بيبطل. طب ليه؟
- هاكر سرق R1. المستخدم الحقيقي استخدم R1 الأول وخد R2.
- الهاكر يحاول يستخدم R1. السيرفر يلاحظ إن R1 اتستخدم قبل كده.
- ده معناه إن فيه نسختين من نفس الـ token في الدنيا، يعني فيه سرقة. فالسيرفر يلغي العيلة كلها (R1 و R2 وأي حاجة اتولدت منهم)، والمستخدم يعمل login تاني.
جرّب السيناريوهين: اسرق refresh وسيب الوقت يعدّي وبعدين خلي الهاكر يستخدمه. وبعدين اعمل reset، واسرق، وخلي الهاكر يستخدمه على طول قبل المستخدم.
كده حتى لو الـ refresh token اتسرق، السرقة بتتكشف أول مرة الاتنين يستخدموه.
| Access Token | Refresh Token | |
|---|---|---|
| العمر | دقايق | أيام أو أسابيع |
| بيتبعت لمين | كل API | Auth server بس |
| النوع غالباً | JWT | Opaque متخزن في الداتابيز |
| تقدر تلغيه | بتستنى يخلص | فوراً |
الـ Access Token قصير عشان لو اتسرق يبوظ بسرعة، والـ Refresh Token بيتغيّر مع كل استخدام عشان لو اتسرق السرقة تنكشف.
3. تخزن الـ Token فين في المتصفح؟
اختار موقف، وشوف الـ token في كل مكان هيحصله إيه:
localStorage.setItem("token", …)—
let token = … (متغيّر في JS)—
Set-Cookie: token=…; HttpOnly—
| المكان | الميزة | العيب |
|---|---|---|
localStorage |
سهل، وبيفضل موجود بعد ما تقفل المتصفح | أي JavaScript في الصفحة يقراه، فأي ثغرة XSS تسرقه |
| Memory (متغيّر في JS) | XSS أصعب شوية يوصله | بيضيع مع أول refresh للصفحة |
| HttpOnly Cookie | الـ JavaScript ميقدرش يقراه خالص | المتصفح بيبعته لوحده، فالـ CSRF رجع، ومحتاج SameSite و CSRF token |
لاحظ إننا رجعنا لنفس المكان. أأمن طريقة لتخزين token في المتصفح هي cookie فيها HttpOnly. يعني عملياً بقت session تاني.
التوصيات العملية
- SPA بتاعتك وعلى نفس الـ domain (زي
shop.comوapi.shop.com): استخدم session cookies عادي. ده بالظبط “SPA mode” في Sanctum. - SPA بتكلم APIs كتير من برا: نمط اسمه BFF (Backend For Frontend). سيرفر صغير بتاعك شايل الـ tokens، والمتصفح بيكلمه بـ session cookie.
- تطبيق موبايل: الـ token في التخزين الآمن بتاع النظام، يعني Keychain في iOS و Keystore في Android.
الـ JWT في localStorage مش “أحدث” من الـ session. هو بس شال حماية HttpOnly. فلو الـ client متصفح وعلى نفس الـ domain، الـ session cookie لسه أحسن اختيار.
4. Session ولا JWT؟
| Session | JWT | |
|---|---|---|
| الداتا متخزنة فين | على السيرفر | جوه الـ token مع المستخدم |
| كل request | lookup في الـ store | تشيك توقيع بس |
| الـ Logout | فوري | صعب |
| تغيير الصلاحيات | بيبان فوراً | بيستنى الـ token يخلص |
| Services كتير | محتاجة store مشترك | كل service تتأكد لوحدها |
| تطبيقات الموبايل | تنفع بس مش طبيعية | طبيعية |
| CSRF | محتاج حماية | مش مشكلة لو في header |
| XSS | HttpOnly بتحميه | في localStorage بيتسرق |
| الحجم | رقم صغير | ممكن يبقى كبير، وبيتبعت مع كل request |
إمتى تختار إيه
- Session: موقع Blade زي shop-lab، أو SPA بتاعتك على نفس الـ domain.
- Opaque Token زي Sanctum: تطبيق موبايل، أو API لعملاء، ومحتاج تلغي tokens بسهولة.
- JWT: services كتير بتتأكد من غير ما تكلم بعض، أو token جاي من Identity Provider زي Google في OpenID Connect.
مفيش حل أحسن من التاني بشكل مطلق. الـ Session بتديك تحكم، والـ JWT بيديك توزيع، واختار على حسب اللي إنت محتاجه فعلاً.
أسئلة الإنترفيو
فكّر في إجابتك الأول، وبعدين افتح السؤال.
01إزاي تعمل logout لـ JWT؟
مفيش طريقة stateless بالكامل. الأساس عمر قصير للـ access token مع refresh token تقدر تلغيه. ولو محتاج إلغاء فوري: denylist بالـ jti في Redis لحد الـ exp، أو token version على المستخدم. وكل حل من دول بيرجّع شوية state.
02ليه فيه Access Token و Refresh Token؟
الـ access token بيتبعت مع كل request فاحتمال سرقته أكبر، فبنخليه قصير. الـ refresh token بيتبعت لمكان واحد بس ونادراً، وبيتخزن على السيرفر فنقدر نلغيه، وبيجدد الـ access token من غير ما المستخدم يعمل login.
03يعني إيه Refresh Token Rotation؟
كل استخدام للـ refresh token بيدي واحد جديد ويبطّل القديم. ولو القديم اتستخدم تاني، يبقى فيه نسختين يعني سرقة، فالسيرفر يلغي العيلة كلها ويجبر المستخدم يعمل login.
04تخزن الـ token فين في الـ SPA؟
localStorage مكشوف لأي XSS. HttpOnly cookie أأمن من XSS بس بترجّع CSRF فمحتاج SameSite و CSRF token. لو الـ SPA بتاعتي وعلى نفس الـ domain أستخدم session cookies (زي Sanctum SPA). ولو APIs برا أستخدم BFF.
05Session ولا JWT؟
JWT مش session أحسن. بينقل الـ state من السيرفر للـ client، فبيكسب تأكيد من غير داتابيز وبيخسر سهولة الإلغاء. بختار Session لموقع أو SPA على نفس الـ domain، و Opaque token للموبايل والـ APIs اللي محتاجة إلغاء سهل، و JWT لما services كتير محتاجة تتأكد من غير ما تكلم بعض.