حل مشكلة 403 Forbidden في Laravel: دليل التشخيص خطوة بخطوة
قبل أن تبدأ بتعديل الأذونات أو ملف .htaccess، اعرف أولًا أن خطأ 403 Forbidden في Laravel له مصدران مختلفان تمامًا، والحل يختلف جذريًا بينهما: إما أن الخادم (Apache/Nginx) يرفض تسليم الملف قبل أن يعمل Laravel أصلًا، أو أن Laravel نفسه يشتغل بشكل سليم لكنه يعيد 403 عمدًا لأن منطق التفويض (Gate/Policy/Middleware) منع المستخدم. الطريقة الأسرع للتفريق: لو رأيت صفحة 403 بتصميم Apache/Nginx الافتراضي، فالمشكلة في الخادم؛ ولو رأيت صفحة خطأ Laravel المنسّقة (أو استجابة JSON فيها "message")، فالمشكلة داخل التطبيق. هذا التمييز وحده يوفّر عليك ساعات من البحث في المكان الخطأ.
متى يكون الخطأ من الخادم (قبل تشغيل Laravel)؟
هذا النوع يظهر عادة عند نشر المشروع على استضافة جديدة. المستخدم يطلب الرابط، فيرفض الخادم فورًا. أهم الأسباب وحلولها:
-
جذر الموقع (document root) لا يشير إلى مجلد
public. هذا السبب الأول والأكثر شيوعًا. Laravel يجب أن يُخدَّم من داخلpublic/فقط، وليس من جذر المشروع. عدّل إعداد الاستضافة أو الـ VirtualHost بحيث يكون الجذر هو/path/to/project/public. لو كنت على استضافة مشتركة، أنشئ رابطًا رمزيًا أو انقل محتوىpublicبحذر مع تعديل مساراتindex.php. -
تعطيل mod_rewrite في Apache. لو كان mod_rewrite غير مفعّل أو
AllowOverrideمضبوطًا علىNone، فلن يُقرأ ملف.htaccessوقد تحصل على 403. فعّل الوحدة بـa2enmod rewriteواضبطAllowOverride Allداخل كتلة<Directory>ثم أعد تشغيل Apache. -
إعداد Nginx بدون
try_files. على Nginx تأكد أن كتلةlocation /تحتوي علىtry_files $uri $uri/ /index.php?$query_string;، وأن مسارrootينتهي بـ/public. -
أذونات أو ملكية خاطئة. يجب أن يملك مستخدم خادم الويب (
www-dataغالبًا) صلاحية القراءة، وأن يملك صلاحية الكتابة علىstorageوbootstrap/cacheتحديدًا.
متى يكون الخطأ من Laravel نفسه (التفويض)؟
هنا التطبيق يعمل تمامًا، لكنه يقرر رفض الوصول. مصادر 403 داخل الكود:
- Gate أو Policy تُعيد
false. أي استدعاء مثلGate::authorize()أو$this->authorize('update', $post)أو$user->can('update', $post)سيطلق استثناءAuthorizationExceptionيترجَم إلى 403 عندما لا يملك المستخدم الصلاحية. - Middleware للتفويض في المسار. وسيط مثل
->middleware('can:update,post')أو وسيط أدوار كـrole:adminيعيد 403 عند الفشل. - استدعاء صريح لـ
abort(403)في متحكم أو وسيط كتبته أنت.
لتشخيص هذا النوع: افتح storage/logs/laravel.log، وابحث عن آخر AuthorizationException. سيخبرك بالسياسة (Policy) والدالة (ability) التي رفضت. غالبًا يكون السبب أن المستخدم غير مسجّل الدخول، أو أن دالة الـ Policy تقارن $user->id بحقل خاطئ، أو أنك نسيت تسجيل الـ Policy في AuthServiceProvider.
جدول: خطأ الخادم أم خطأ Laravel؟
| العلامة | 403 من الخادم | 403 من Laravel |
|---|---|---|
| شكل الصفحة | تصميم Apache/Nginx افتراضي | صفحة Laravel منسّقة أو JSON |
يظهر في سجل laravel.log؟ | لا | نعم (AuthorizationException) |
| متى يحدث غالبًا؟ | بعد النشر مباشرة | بعد تسجيل الدخول أو على مسار محمي |
| مكان الإصلاح | إعداد الخادم والأذونات | Policy / Gate / Middleware |
| يتأثر بالمستخدم؟ | لا، يظهر للجميع | نعم، حسب صلاحية المستخدم |
خطوات التشخيص السريع
- افتح أدوات المطوّر في المتصفح (تبويب Network) وانظر لرأس الاستجابة
Serverوشكل جسم الرسالة لتحدد المصدر. - راجع
storage/logs/laravel.log؛ وجودُ استثناء يعني أن Laravel وصل ونفّذ الطلب. - لو لم يصل الطلب إلى Laravel، راجع سجلات الخادم:
/var/log/apache2/error.logأو/var/log/nginx/error.log. - تأكد أن الجذر يشير إلى
public، وأن.htaccessالأصلي الخاص بـ Laravel موجود داخلpublic/. - لو المشكلة تفويضية، تتبّع الـ Policy المسؤولة وتحقق من تسجيل الدخول وحالة
$user.
خطأ شائع يجب تجنّبه
الطريقة التي تنتشر في المقالات القديمة — تنفيذ chmod -R 644 على كامل المشروع — خطأ يكسر الموقع، لأن المجلدات تحتاج صلاحية التنفيذ (x) كي تُفتَح، فتحويلها إلى 644 يسبب 403 بدل أن يحلّه. الصحيح فصلُ المجلدات عن الملفات:
find . -type d -exec chmod 755 {} \;
find . -type f -exec chmod 644 {} \;
ولا تلجأ أبدًا إلى chmod -R 777 كحل سريع؛ فهو ثغرة أمنية خطيرة على أي خادم إنتاج. المطلوب فقط أن تكون storage وbootstrap/cache قابلة للكتابة من مستخدم خادم الويب.
الأسئلة الشائعة
لماذا يظهر 403 على الاستضافة المشتركة رغم أن المشروع يعمل محليًا؟
غالبًا لأن جذر النطاق يشير إلى مجلد المشروع بدل public، أو لأن AllowOverride معطّل فلا يُقرأ .htaccess. أنشئ رابطًا رمزيًا للجذر إلى public أو راجع الدعم الفني للاستضافة.
كيف أعرف أن السبب تفويض وليس الخادم؟
لو ظهر الاستثناء في laravel.log فالسبب تفويضي، ولو كانت الصفحة بتصميم الخادم الافتراضي ولا شيء في سجل Laravel فالسبب في إعداد الخادم.
عطّلت الجدار الناري ولم يُحلّ الخطأ، ماذا أفعل؟
الجدار الناري نادرًا ما يسبب 403 (يسبب عادة انقطاعًا أو مهلة). ابدأ بجذر public والأذونات وسجلات الخطأ قبل لمس الجدار الناري.
أرى 403 عند استدعاء API بصيغة JSON فقط، لماذا؟
هذا مؤشر واضح على تفويض داخل Laravel؛ راجع الوسيط can: أو استدعاء authorize() في المتحكم، وتأكد من إرسال رمز المصادقة (Token) الصحيح في رأس الطلب.