عميل يكلّمك: «كل يوم أفتح المنتج وألقى نفسي طالع من حسابي». أو يشتغل عادي، وفجأة تنقلب الصفحة لصفحة الدخول. تفتح Console وتلقى: AuthApiError: Invalid Refresh Token: Refresh Token Not Found، أو أختها Invalid Refresh Token: Already Used. هذا مو نفس خطأ JWT expired: هناك المنتج ما جدّد الجلسة، وهنا المنتج حاول يجدّدها وSupabase رفض.
إيش يعني بالضبط
لما المستخدم يسجّل دخول، ياخذ توكن دخول يعيش ساعة افتراضيًا، وتوكن تجديد يطلب به المنتج توكن دخول جديد بدون ما يسجّل المستخدم من جديد. توكن التجديد يُستخدم مرة وحدة فقط: كل تجديد يعطي زوج جديد، والقديم ينتهي.
- Refresh Token Not Found: Supabase ما لقى الجلسة اللي يتبع لها التوكن. غالبًا لأن الجلسة انحذفت، مثلًا لأن المستخدم سجّل خروج، من هذا الجهاز أو من جهاز ثاني.
- Already Used: التوكن انستخدم قبل. Supabase يسمح باستخدامه مرة ثانية خلال 10 ثواني افتراضيًا، عشان طلبين متقاربين ما يتعارضون. بعد هالمدة يعتبرها علامة إن التوكن انسرق، ويلغي الجلسة كلها.
في الحالتين، لما يفشل التجديد وتوكن الدخول منتهي، مكتبة Supabase تمسح الجلسة من المتصفح، والمستخدم يلقى نفسه طالع. أحيانًا هذا صحيح: المستخدم فعلًا سجّل خروج. المشكلة لما يصير وهو ما سوّى شي.
ليش يصير في المنتجات المبنية بالذكاء الاصطناعي
- تسجيل الخروج يطلّعه من كل أجهزته:
signOut()بدون خيار يطلّع المستخدم افتراضيًا من كل أجهزته (global)، ويلغي كل جلساته. يسجّل خروج من جواله، فاللابتوب يطلع له Refresh Token Not Found. - أكثر من نسخة من مكتبة Supabase: الكود ينشئ
createClientفي أكثر من مكان، وكل نسخة تدير الجلسة لحالها. نسختين يجدّدون نفس التوكن: وحدة تنجح، والثانية ترسل توكن انتهى. - تجديد يدوي فوق التجديد التلقائي: الأداة تضيف
refreshSession()في كل صفحة أو كل كم دقيقة، والمكتبة أصلًا تجدّد بنفسها. - التوكن انحفظ نسخة: الكود حفظ توكن التجديد بنفسه، في متغير أو في قاعدة البيانات، ويستخدمه بعد ما تغيّر.
- Next.js والسيرفر: في المنتجات المبنية بـ v0 أو Cursor على Next.js مع مكتبة
@supabase/ssr، الجلسة تنحفظ في الكوكيز بدلlocalStorage، عشان السيرفر يقراها. الصفحات اللي تنبني على السيرفر ما تقدر تكتب كوكيز، فملف proxy، واسمه middleware قبل Next.js 16، هو اللي يجدّد ويرسل الكوكيز الجديدة للمتصفح. إذا الملف يرجّع رد غير اللي فيه الكوكيز الجديدة، المتصفح يبقى بالتوكن القديم ويرسله مرة ثانية.
كيف تتأكد بنفسك
- اضغط F12 وافتح Console، وانسخ الرسالة كاملة: Not Found ولا Already Used. وإذا لقيت
Session Expired، فالسبب غالبًا إعدادات مدة الجلسة في Supabase، وهي متوفرة في الخطط المدفوعة. - في نفس الـ Console، دوّر على تحذير
Multiple GoTrueClient instances detected. هذا تحذير من المكتبة نفسها إن فيه أكثر من نسخة. - افتح Network ودوّر على طلب فيه
token?grant_type=refresh_token. الطلب المرفوض يرجع 400، وفي الردerror_codeمثلrefresh_token_already_used. إذا شفت طلبين تجديد طالعين مع بعض، هذا هو التعارض. - افتح Application: تحت Local Storage، أو تحت Cookies في مشاريع Next.js، بتلقى مفتاح يبدأ بـ
sb-وينتهي بـ-auth-token، وممكن ينقسم لأكثر من جزء مثل.0و.1. هنا ساكنة الجلسة. - جرّب بجهازين: سجّل دخول في الاثنين، واطلع من واحد، وارجع للثاني. إذا طلع منه، السبب تسجيل الخروج من كل الأجهزة.
- إذا كل المستخدمين طلعوا في نفس الوقت، اسأل إذا أحد غيّر JWT secret في إعدادات المشروع. Supabase يقول إن تغييره يطلّع كل المستخدمين المسجّلين فورًا.
الحل اللي لا تسويه
- تعطيل كشف إعادة الاستخدام أو تطويل الـ 10 ثواني. Supabase نفسه ما ينصح فيه. هذي الحماية اللي تكشف توكن مسروق، وإذا عطّلتها، التوكن المسروق يشتغل مثل الأصلي.
- تقول للمستخدمين «امسحوا الكاش». يختفي الخطأ عند واحد، ويرجع عند الكل.
- إعادة المحاولة بنفس التوكن. التوكن انرفض، وكل محاولة بيرجع لها نفس الخطأ.
- تطويل مدة توكن الدخول لأيام. هذا يأجّل التجديد، لكن يخلّي أي توكن ينسرق صالح أطول. شرحنا ليش في JWT expired.
الحل الصح
- نسخة وحدة من مكتبة Supabase للمنتج كله، في ملف واحد، وكل الصفحات تستخدمها.
- خلّ المكتبة تجدّد بنفسها. احذف أي
refreshSession()يدوي، وأي نسخة محفوظة من التوكن. - زر تسجيل الخروج العادي يكون
signOut({ scope: 'local' })، يعني هذا الجهاز فقط. وخلّ الخروج من كل الأجهزة زر مستقل يختاره المستخدم. - في Next.js، طبّق ملف proxy بطريقة Supabase الرسمية، وتأكد إنه يرجّع نفس الرد اللي انكتبت فيه الكوكيز الجديدة.
- لما تنتهي الجلسة فعلًا، استمع لحدث
SIGNED_OUTعن طريقonAuthStateChange، ووجّه المستخدم لصفحة الدخول برسالة واضحة، بدل صفحة فاضية أو أخطاء متتالية.
فحوصات ثانية على تسجيل الدخول تلقاها في 5 ثغرات في تسجيل الدخول.
تبي أحد يتأكد منها؟
إذا عرفت الرسالة وما عرفت من وين تجي، مكالمة استشارية نتتبّع فيها طلبات التجديد معك حتى نلقى النسخة اللي تتعارض. إذا الجلسات متلخبطة بين المتصفح والسيرفر في أكثر من مكان، فيه الإصلاح المباشر. وإذا منتجك مبني بـ Lovable، شوف مشاكل Lovable و Supabase، وإذا بـ v0، شوف مشاكل v0 الأشهر.