الـ Middleware وتسجيل الدخول في Laravel
مدونتك تعرض المقالات وتحفظها، لكن أي زائر يستطيع الآن أن يفتح صفحة «مقال جديد» ويكتب ما يشاء! في هذا الدرس ستتعلم كيف تحمي صفحاتك: ستبني تسجيل دخول وخروج، وتغلق الصفحات الخاصة أمام الزوار، وتكتب حارساً خاصاً لصفحات المدير.
ما هو الـ Middleware؟
Section titled “ما هو الـ Middleware؟”الـ Middleware (الوسيط) كود يقف بين الطلب والمتحكم: يفحص كل طلب قبل أن يصل، ثم يقرر: هل يمر، أم يُرفض، أم يُحوَّل إلى مكان آخر؟
تخيّل بوابات الأمن في المطار: قبل أن تصل إلى الطائرة تمر على فحص التذكرة، ثم فحص الجواز، ثم فحص الحقائب. كل بوابة تتأكد من شيء واحد، ومن يفشل في واحدة لا يكمل.
Laravel يشغّل وسطاء كثيرين دون أن تشعر: الفحص الذي يرفض النماذج بلا @csrf (من الدرس السادس) وسيط، وتشغيل الجلسات وسيط. وأشهر وسيط ستستخدمه بنفسك اسمه auth: يسمح بالمرور لمن سجّل دخوله فقط.
تجهيز مستخدم للتجربة
Section titled “تجهيز مستخدم للتجربة”Laravel يأتي ومعه جدول users و Model اسمه User جاهزان. لننشئ مستخدماً من الطرفية بالأمر php artisan tinker:
User::create([ 'name' => 'سارة', 'password' => 'secret123',]);افتح قاعدة البيانات وانظر إلى عمود password: لن تجد secret123، بل نصاً طويلاً يبدأ بـ $2y$. الـ Model يشفّر كلمة السر تلقائياً قبل حفظها، لأن فيه الإعداد 'password' => 'hashed'. وهذا التشفير باتجاه واحد: لا أحد يستطيع استرجاع الكلمة الأصلية، حتى أنت.
مسارات الدخول والخروج
Section titled “مسارات الدخول والخروج”use App\Http\Controllers\AuthController;
Route::get('/login', [AuthController::class, 'showLogin'])->name('login');Route::post('/login', [AuthController::class, 'login']);Route::post('/logout', [AuthController::class, 'logout'])->name('logout');اسم المسار login مهم: الوسيط auth يبحث عن مسار بهذا الاسم ليحوّل إليه الزوار.
المتحكم: Auth::attempt
Section titled “المتحكم: Auth::attempt”أنشئ المتحكم بـ php artisan make:controller AuthController واكتب فيه:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;use Illuminate\Support\Facades\Auth;
class AuthController extends Controller{ public function showLogin() { return view('auth.login'); }
public function login(Request $request) { $credentials = $request->validate([ 'email' => 'required|email', 'password' => 'required', ]);
if (Auth::attempt($credentials)) { $request->session()->regenerate();
return redirect()->intended('/dashboard'); }
return back() ->withErrors(['email' => 'البريد أو كلمة السر غير صحيحة']) ->onlyInput('email'); }
public function logout(Request $request) { Auth::logout(); $request->session()->invalidate(); $request->session()->regenerateToken();
return redirect('/login'); }}لنفهم الدالة login:
Auth::attemptتبحث عن مستخدم بهذا البريد، وتقارن كلمة السر بالنسخة المشفّرة. إذا تطابقتا تسجّل دخوله وتعيدtrue.session()->regenerate()تغيّر معرّف الجلسة بعد الدخول، للحماية من هجوم تثبيت الجلسة.redirect()->intended('/dashboard')لمسة ذكية: إذا كان الزائر قد حاول فتح/profileفحُوِّل إلى صفحة الدخول، فستعيده بعد الدخول إلى/profileالتي أرادها. وإن لم يكن، فإلى/dashboard.- عند الفشل نعود برسالة خطأ، و
onlyInput('email')تحتفظ بالبريد المكتوب دون كلمة السر.
والنموذج قالب Blade عادي، كما تعلمت في الدرس السادس:
<form method="POST" action="/login"> @csrf
<label>البريد الإلكتروني</label> <input type="email" name="email" value="{{ old('email') }}"> @error('email') <p class="error">{{ $message }}</p> @enderror
<label>كلمة السر</label> <input type="password" name="password">
<button type="submit">دخول</button></form>حماية المسارات بـ auth
Section titled “حماية المسارات بـ auth”الآن نضع الصفحات الخاصة داخل مجموعة عليها الوسيط auth:
Route::middleware('auth')->group(function () { Route::get('/dashboard', function () { return view('dashboard'); })->name('dashboard');
Route::get('/profile', function () { return 'ملف ' . auth()->user()->name; });});النتيجة عند التجربة:
| الحالة | ما يحدث |
|---|---|
زائر يفتح /dashboard |
يُحوَّل إلى /login |
| دخول بكلمة سر خاطئة | يعود للنموذج ومعه رسالة الخطأ |
| دخول ببيانات صحيحة | يُحوَّل إلى /dashboard |
مستخدم مسجّل يفتح /profile |
يرى «ملف سارة» |
| بعد الخروج | تُغلق الصفحات من جديد |
الدالة auth()->user() تعطيك كائن المستخدم الحالي في أي مكان. داخل المجموعة المحمية تضمن أنه موجود.
في القوالب: @auth و @guest
Section titled “في القوالب: @auth و @guest”كثيراً ما تريد عرض أشياء مختلفة للمستخدم المسجّل وللزائر، مثل زر الدخول أو الخروج:
@auth <p>أهلاً {{ auth()->user()->name }}</p> <form method="POST" action="/logout"> @csrf <button type="submit">تسجيل الخروج</button> </form>@endauth
@guest <a href="/login">تسجيل الدخول</a>@endguestلاحظ أن الخروج نموذج POST وليس رابطاً عادياً، وفيه @csrf. لو كان رابط GET لاستطاع أي موقع خبيث أن يسجّل خروج زوارك بصورة مخفية.
Middleware خاص: صفحات المدير
Section titled “Middleware خاص: صفحات المدير”الوسيط auth يسأل «هل سجّلت دخولك؟». لكن لوحة الإدارة تحتاج سؤالاً آخر: «هل أنت مدير؟». أضف أولاً عموداً للمستخدمين بملف Migration جديد:
$table->boolean('is_admin')->default(false);ثم أنشئ الوسيط بالأمر php artisan make:middleware EnsureIsAdmin:
public function handle(Request $request, Closure $next): Response{ if (! $request->user()?->is_admin) { abort(403, 'هذه الصفحة للمدير فقط'); }
return $next($request);}كل وسيط له الدالة handle: إما أن يوقف الطلب (هنا بخطأ 403، ومعناه «ممنوع»)، وإما أن يستدعي $next($request) ليمرره إلى البوابة التالية. أعطِ الوسيط اسماً مختصراً:
->withMiddleware(function (Middleware $middleware): void { $middleware->alias([ 'admin' => \App\Http\Middleware\EnsureIsAdmin::class, ]);})واستخدمه مع auth:
Route::get('/admin', function () { return 'لوحة المدير';})->middleware(['auth', 'admin']);المستخدم العادي يرى صفحة خطأ 403 فيها رسالتنا، والمدير يرى «لوحة المدير». ولجعل مستخدم مديراً من tinker: $user->is_admin = true; $user->save();.
جرّب بنفسك
Section titled “جرّب بنفسك”- انقل مسارات
/posts/createوPOST /postsإلى داخل مجموعةauth، وتأكد أن الزائر لا يستطيع إضافة مقال. - أضف صفحة تسجيل حساب جديد: نموذج فيه الاسم والبريد وكلمة السر، ثم
User::create(...)ثمAuth::login($user). - أضف لجدول
postsعمودuser_id، واحفظ فيهauth()->id()عند إنشاء المقال، واعرض اسم الكاتب تحت العنوان بعلاقةbelongsTo.
أخطاء شائعة
Section titled “أخطاء شائعة”- مسار الدخول بلا اسم
login: الوسيطauthيبحث عن هذا الاسم، وبدونه يظهر الخطأRoute [login] not defined. - إضافة
is_adminإلى القائمة المسموح ملؤها: عندها يستطيع أي مسجّل جديد أن يرسلis_admin=1ويصبح مديراً! اتركه خارجها، وعيّنه يدوياً. - الخروج برابط GET: اجعله نموذج POST مع
@csrf. - الاكتفاء بإخفاء الزر: إخفاء زر «حذف» بـ
@authلا يحمي المسار نفسه. الحماية الحقيقية بالوسيط على المسار.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
ماذا يحدث عندما يفتح زائر غير مسجّل مساراً عليه الوسيط auth؟
الوسيط auth يوقف الطلب، ويحوّل الزائر إلى صفحة الدخول، ويتذكر الصفحة التي أرادها.
سؤال
ما دور $next($request) داخل الوسيط؟
استدعاء $next يعني أن الطلب اجتاز هذه البوابة، فيكمل طريقه إلى الوسيط التالي أو المتحكم.
الخلاصة
Section titled “الخلاصة”- الوسيط كود يفحص الطلب قبل وصوله إلى المتحكم.
Auth::attemptتتحقق من البيانات وتسجّل الدخول، وAuth::logoutللخروج.Route::middleware('auth')->group(...)تحمي مجموعة مسارات.@authو@guestوauth()->user()للتعامل مع المستخدم الحالي.- تكتب وسيطك الخاص بـ
make:middleware، وتسجّل اسمه فيbootstrap/app.php.