كيفية حظر المستخدم مؤقتًا في Laravel باستخدام Middleware وbanned_until
توفر Laravel نظام مصادقة Authentication متكاملًا يمكن من خلاله تسجيل الدخول والخروج وإدارة جلسات المستخدمين بسهولة، كما يمكن توسيع هذا النظام وإضافة قواعد خاصة حسب متطلبات التطبيق.
من الميزات التي قد نحتاج إليها في بعض التطبيقات إمكانية حظر مستخدم مؤقتًا ومنعه من الوصول إلى النظام حتى وقت أو تاريخ معين، ثم فك الحظر عنه تلقائيًا بعد انتهاء المدة المحددة.
على سبيل المثال، قد نرغب في حظر المستخدم حتى:
2026-09-10 15:00:00
وبعد الوصول إلى هذا الوقت يصبح بإمكانه استخدام حسابه مرة أخرى دون الحاجة إلى قيام المدير بفك الحظر يدويًا.
سننفذ ذلك باستخدام:
- حقل
banned_untilداخل جدولusers. - Datetime Cast داخل
User Model. - Middleware لفحص حالة المستخدم.
- Carbon للمقارنة بين التواريخ.
- تسجيل الخروج الآمن عند اكتشاف مستخدم محظور.
- إمكانية الحظر وفك الحظر من لوحة التحكم.
الفكرة الأساسية بسيطة: إذا كانت قيمة banned_until أكبر من الوقت الحالي، فالحساب ما زال محظورًا. أما إذا انتهى التاريخ، فلا نحتاج إلى تعديل أي شيء؛ المستخدم يصبح غير محظور تلقائيًا.
كيف سيعمل نظام الحظر؟
لنفترض أن الوقت الحالي هو:
2026-09-01 10:00:00
ولدينا مستخدم قيمة banned_until الخاصة به:
2026-09-05 10:00:00
بما أن:
banned_until > now()
فالمستخدم ما زال محظورًا.
أما إذا أصبحت القيمة:
2026-08-30 10:00:00
فإن تاريخ الحظر انتهى، وبالتالي يمكن السماح للمستخدم باستخدام النظام بشكل طبيعي.
المخطط العام
User Request
│
▼
Authentication
│
▼
CheckBanned Middleware
│
├── banned_until = null
│ │
│ └── Allow Request
│
├── banned_until <= now()
│ │
│ └── Allow Request
│
└── banned_until > now()
│
▼
Logout
│
▼
Invalidate Session
│
▼
Redirect to Login
الخطوة الأولى: إضافة banned_until إلى جدول users
نقوم بإنشاء Migration جديد:
php artisan make:migration add_banned_until_to_users_table --table=users
ثم داخل ملف Migration:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('users', function (Blueprint $table) {
$table->timestamp('banned_until')
->nullable();
});
}
public function down(): void
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('banned_until');
});
}
};
ثم ننفذ:
php artisan migrate
لماذا nullable؟
لأن معظم المستخدمين غير محظورين، وبالتالي يمكن أن تكون القيمة:
NULL
والحالات تصبح كالتالي:
banned_until = NULL
→ المستخدم غير محظور
banned_until = تاريخ مستقبلي
→ المستخدم محظور
banned_until = تاريخ انتهى
→ انتهى الحظر
الخطوة الثانية: إضافة Cast داخل User Model
حتى نستطيع التعامل مع banned_until ككائن تاريخ باستخدام Carbon، نضيف له datetime cast.
داخل:
app/Models/User.php
نضيف:
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'banned_until' => 'datetime',
];
}
بعد ذلك يستطيع Laravel تحويل:
$user->banned_until
إلى DateTime / Carbon object يمكن استخدام عمليات التاريخ عليه.
مثال
$user->banned_until->isFuture();
$user->banned_until->diffForHumans();
$user->banned_until->diffInDays(now());
في المشاريع الحديثة لا تحتاج إلى استخدامprotected $datesلهذا الغرض؛ استخدام الـdatetime cast أوcasts()هو الأسلوب الأوضح والأحدث.
هل نضيف banned_until إلى $fillable؟
يمكن إضافته:
protected $fillable = [
'name',
'email',
'password',
'banned_until',
];
لكن في التطبيقات الحقيقية يجب التفكير في الجانب الأمني.
إذا كان المستخدم يستطيع تحديث بيانات حسابه باستخدام:
User::update($request->all());
فوجود:
banned_until
داخل $fillable قد يسمح بحدوث Mass Assignment غير مرغوب فيه إذا لم يتم التحكم بالمدخلات جيدًا.
لذلك يمكن عدم إضافته إلى $fillable أصلًا إذا كان يتم تعديله فقط بواسطة الإدارة.
ثم تعديله بشكل صريح:
$user->banned_until = now()->addDays(7);
$user->save();
لا تستخدم $request->all() لتحديث المستخدمين، خصوصًا إذا كانت هناك حقول إدارية مثل role أو is_admin أو banned_until.
الخطوة الثالثة: إنشاء Middleware
نقوم بإنشاء Middleware:
php artisan make:middleware CheckBanned
سيتم إنشاء الملف غالبًا داخل:
app/Http/Middleware/CheckBanned.php
نعدل محتواه:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class CheckBanned
{
public function handle(
Request $request,
Closure $next
): Response {
$user = $request->user();
if (
$user &&
$user->banned_until &&
$user->banned_until->isFuture()
) {
$bannedUntil = $user->banned_until;
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect()
->route('login')
->with(
'message',
'Your account is suspended until '
.$bannedUntil->format('Y-m-d H:i:s')
);
}
return $next($request);
}
}
شرح CheckBanned Middleware
الحصول على المستخدم الحالي
$user = $request->user();
إذا لم يكن المستخدم مسجلًا، ستكون القيمة:
null
التأكد من وجود banned_until
$user->banned_until
إذا كانت:
null
فالحساب غير محظور.
التأكد أن تاريخ الحظر ما زال في المستقبل
$user->banned_until->isFuture()
وهو يعبر بصورة واضحة عن الشرط:
banned_until > now()
لماذا لا نكتفي بـAuth::logout()؟
عند إجبار مستخدم على تسجيل الخروج بسبب الحظر، الأفضل إنهاء Session الحالية بصورة صحيحة.
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
لدينا ثلاث خطوات:
1. تسجيل الخروج
Auth::logout();
2. إبطال Session
$request->session()->invalidate();
3. إنشاء CSRF Token جديد
$request->session()->regenerateToken();
وهذا أفضل أمنيًا من تنفيذ Logout فقط.
عرض المدة المتبقية من الحظر
يمكن استخدام Carbon لعرض المدة بصورة سهلة.
عرض الفرق بصورة بشرية
$remaining = $user
->banned_until
->diffForHumans();
قد تكون النتيجة مثل:
3 days from now
أو يمكن حساب عدد الأيام:
$days = now()
->diffInDays(
$user->banned_until
);
إظهار رسالة
$message =
'Your account is suspended until '
.$user->banned_until->format('Y-m-d H:i:s');
تحسين الرسالة بالعربية
$message =
'تم تعليق حسابك حتى '
.$user->banned_until->format('Y-m-d H:i:s');
أو:
$message =
'تم تعليق حسابك مؤقتًا. سيتم فك الحظر '
.$user->banned_until->diffForHumans();
الخطوة الرابعة: تسجيل Middleware في Laravel 12
في Laravel الحديث يمكن تعريف Alias للـMiddleware داخل:
bootstrap/app.php
مثال:
<?php
use App\Http\Middleware\CheckBanned;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Middleware;
return Application::configure(
basePath: dirname(__DIR__)
)
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
)
->withMiddleware(
function (Middleware $middleware) {
$middleware->alias([
'not-banned' => CheckBanned::class,
]);
}
)
->create();
بعد ذلك نستطيع استخدام:
not-banned
داخل Routes.
إذا كنت تعمل على إصدار Laravel أقدم، فقد تجد تسجيل Middleware داخلapp/Http/Kernel.php. أما في بنية Laravel الحديثة فيتم ضبط Middleware منbootstrap/app.php.
تطبيق Middleware على Routes المحمية
يمكن وضع:
auth
و:
not-banned
معًا:
Route::middleware([
'auth',
'not-banned'
])->group(function () {
Route::get(
'/dashboard',
function () {
return view('dashboard');
}
)->name('dashboard');
});
مخطط تنفيذ الطلب
GET /dashboard
│
▼
auth middleware
│
├── Not Authenticated
│ │
│ └── Login
│
▼
CheckBanned
│
├── Banned
│ │
│ └── Logout
│
▼
Dashboard
هل يجب وضع CheckBanned على كل الموقع؟
ليس بالضرورة.
الأفضل غالبًا تطبيقه فقط على Routes التي تحتاج إلى مستخدم مسجل:
Route::middleware([
'auth',
'not-banned'
])->group(function () {
// authenticated application routes
});
وهذا أوضح من تشغيل فحص المستخدم المحظور على صفحات عامة مثل:
/
login
register
forgot-password
إنشاء Login Controller بشكل صحيح
إذا كنت تقوم ببناء Login يدويًا، يمكن كتابة:
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
public function store(Request $request)
{
$credentials = $request->validate([
'email' => [
'required',
'email',
],
'password' => [
'required',
'string',
],
]);
if (! Auth::attempt($credentials)) {
return back()
->withErrors([
'email' =>
'البريد الإلكتروني أو كلمة المرور غير صحيحة.',
])
->onlyInput('email');
}
$request->session()->regenerate();
return redirect()
->intended(
route(
'dashboard',
absolute: false
)
);
}
}
لماذا نستخدم session()->regenerate()؟
بعد نجاح تسجيل الدخول يجب إنشاء Session ID جديد للمساعدة في الحماية من هجمات:
Session Fixation
Routes تسجيل الدخول
use App\Http\Controllers\Auth\LoginController;
Route::get(
'/login',
function () {
return view('auth.login');
}
)->name('login');
Route::post(
'/login',
[LoginController::class, 'store']
)->name('login.store');
Form تسجيل الدخول
<div class="card">
<div class="card-header">
تسجيل الدخول
</div>
<div class="card-body">
@if (session('message'))
<div class="alert alert-danger">
{{ session('message') }}
</div>
@endif
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>
{{ $error }}
</li>
@endforeach
</ul>
</div>
@endif
<form
method="POST"
action="{{ route('login.store') }}"
>
@csrf
<div>
<label>
البريد الإلكتروني
</label>
<input
type="email"
name="email"
value="{{ old('email') }}"
required
>
</div>
<div>
<label>
كلمة المرور
</label>
<input
type="password"
name="password"
required
>
</div>
<button type="submit">
تسجيل الدخول
</button>
</form>
</div>
</div>
حظر مستخدم لمدة يوم
$user->banned_until = now()
->addDay();
$user->save();
حظر لمدة 7 أيام
$user->banned_until = now()
->addDays(7);
$user->save();
حظر لمدة شهر
$user->banned_until = now()
->addMonth();
$user->save();
الحظر حتى تاريخ محدد
$user->banned_until =
'2026-12-01 12:00:00';
$user->save();
وبسبب:
'banned_until' => 'datetime'
سيتعامل Eloquent مع الحقل كتاريخ.
فك الحظر يدويًا
الأمر بسيط:
$user->banned_until = null;
$user->save();
بمجرد أن تصبح القيمة:
NULL
فإن Middleware لن يعتبر المستخدم محظورًا.
إضافة Ban وUnban إلى لوحة التحكم
Routes
Route::middleware([
'auth',
'not-banned',
])->group(function () {
Route::post(
'/admin/users/{user}/ban',
[UserBanController::class, 'store']
)->name('users.ban');
Route::delete(
'/admin/users/{user}/ban',
[UserBanController::class, 'destroy']
)->name('users.unban');
});
في التطبيق الحقيقي يجب إضافة Authorization مثل Policy أو Gate حتى لا يستطيع المستخدم العادي تنفيذ عمليات Ban وUnban.
Controller لحظر المستخدم
<?php
namespace App\Http\Controllers;
use App\Models\User;
use Illuminate\Http\Request;
class UserBanController extends Controller
{
public function store(
Request $request,
User $user
) {
$data = $request->validate([
'banned_until' => [
'required',
'date',
'after:now',
],
]);
$user->banned_until =
$data['banned_until'];
$user->save();
return back()->with(
'success',
'تم حظر المستخدم بنجاح.'
);
}
public function destroy(User $user)
{
$user->banned_until = null;
$user->save();
return back()->with(
'success',
'تم فك حظر المستخدم.'
);
}
}
لماذا نستخدم after:now؟
حتى لا يستطيع المدير إرسال تاريخ حظر انتهى بالفعل.
'banned_until' => [
'required',
'date',
'after:now',
]
بذلك يجب أن يكون التاريخ في المستقبل.
إضافة Helper إلى User Model
بدل تكرار:
$user->banned_until &&
$user->banned_until->isFuture()
في عدة أماكن، يمكن إنشاء Method داخل User:
public function isBanned(): bool
{
return $this->banned_until !== null
&& $this->banned_until->isFuture();
}
ثم يصبح Middleware أبسط:
if ($user && $user->isBanned()) {
// logout
}
User Model
<?php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
'banned_until' => 'datetime',
];
}
public function isBanned(): bool
{
return $this->banned_until !== null
&& $this->banned_until->isFuture();
}
}
تحسين Middleware باستخدام isBanned()
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class CheckBanned
{
public function handle(
Request $request,
Closure $next
): Response {
$user = $request->user();
if ($user && $user->isBanned()) {
$bannedUntil =
$user->banned_until;
Auth::logout();
$request
->session()
->invalidate();
$request
->session()
->regenerateToken();
return redirect()
->route('login')
->with(
'message',
'تم تعليق حسابك حتى '
.$bannedUntil
->format('Y-m-d H:i:s')
);
}
return $next($request);
}
}
الحظر عند تسجيل الدخول أم بعد تسجيل الدخول؟
هناك طريقتان شائعتان.
الطريقة الأولى: السماح بالمصادقة ثم Middleware
Login
│
▼
Auth::attempt()
│
▼
Dashboard Request
│
▼
CheckBanned
│
└── Banned → Logout
هذه الطريقة سهلة وتضمن منع المستخدم المحظور من الوصول إلى Routes المحمية.
الطريقة الثانية: منع تسجيل الدخول من الأساس
يمكن أيضًا فحص الحساب مباشرة بعد نجاح Auth::attempt().
if (Auth::attempt($credentials)) {
$request->session()->regenerate();
$user = $request->user();
if ($user->isBanned()) {
$bannedUntil =
$user->banned_until;
Auth::logout();
$request
->session()
->invalidate();
$request
->session()
->regenerateToken();
return back()
->withErrors([
'email' =>
'الحساب محظور حتى '
.$bannedUntil
->format('Y-m-d H:i:s'),
]);
}
return redirect()
->intended('/dashboard');
}
هذه الطريقة تجعل تجربة المستخدم أوضح لأنه يعرف مباشرة أثناء تسجيل الدخول أن الحساب محظور.
حتى إذا فحصت الحظر أثناء Login، من الأفضل أيضًا وجود Middleware على Routes الحساسة؛ لأن المستخدم قد تكون لديه Session نشطة ثم يقوم المدير بحظره أثناء وجوده داخل النظام.
لماذا Middleware مهم حتى مع فحص Login؟
لنفترض أن المستخدم قام بتسجيل الدخول الساعة:
10:00
ثم قام المدير بحظره الساعة:
10:15
إذا كان فحص الحظر موجودًا فقط في Login، سيبقى المستخدم قادرًا على استخدام Session الحالية.
أما مع Middleware:
User request
│
▼
CheckBanned
│
▼
Database / User state
│
├── Not banned → Continue
│
└── Banned → Logout
فسيتم منعه في أول Request لاحق.
الحظر الدائم والحظر المؤقت
إذا كان التطبيق يحتاج إلى حظر دائم أيضًا، توجد عدة طرق لتصميم ذلك.
مثال:
is_banned
banned_until
لكن الأفضل غالبًا أن تجعل التصميم واضحًا حسب احتياجات المشروع.
مثال
$table->boolean('is_banned')
->default(false);
$table->timestamp('banned_until')
->nullable();
في هذه الحالة:
is_banned = true
banned_until = null
→ حظر دائم
is_banned = true
banned_until = future date
→ حظر مؤقت
لكن إذا كان التطبيق يحتاج فقط إلى الحظر المؤقت، فوجود banned_until وحده أبسط.
إضافة سبب الحظر
في التطبيقات الإدارية من المفيد تسجيل سبب الحظر.
يمكن إضافة:
$table->text('ban_reason')
->nullable();
ثم:
$user->banned_until =
now()->addDays(7);
$user->ban_reason =
'Violation of platform rules';
$user->save();
تسجيل من قام بالحظر
إذا كان لديك أكثر من Administrator فمن المفيد أيضًا معرفة من قام بتنفيذ العملية.
يمكن إضافة:
banned_by
بحيث يشير إلى ID المدير.
مثال:
$table->foreignId('banned_by')
->nullable()
->constrained('users')
->nullOnDelete();
وعند الحظر:
$user->banned_until =
now()->addDays(7);
$user->banned_by =
auth()->id();
$user->save();
تصميم أكثر احترافية: Ban History
إذا كان المشروع كبيرًا ولا تريد معرفة حالة الحظر الحالية فقط، وإنما تريد الاحتفاظ بتاريخ جميع عمليات الحظر السابقة، فالأفضل إنشاء جدول مستقل.
مثل:
user_bans
+----------------+
| id |
| user_id |
| banned_by |
| reason |
| starts_at |
| ends_at |
| revoked_at |
| created_at |
| updated_at |
+----------------+
بهذه الطريقة تستطيع معرفة:
- كم مرة تم حظر المستخدم.
- من قام بالحظر.
- سبب كل عملية حظر.
- مدة كل حظر.
- هل تم فك الحظر يدويًا.
الحقل banned_until ممتاز للتطبيقات الصغيرة والمتوسطة. أما الأنظمة التي تحتاج Audit Trail كاملًا فقد يكون جدول منفصل لعمليات الحظر أكثر ملاءمة.
التعامل مع API
إذا كان التطبيق عبارة عن API، فقد لا يكون من الصحيح إعادة المستخدم إلى صفحة:
/login
بل يمكن إعادة Response من نوع JSON.
if ($user && $user->isBanned()) {
return response()->json([
'message' => 'Account suspended.',
'banned_until' =>
$user->banned_until,
], 403);
}
HTTP Status
يمكن استخدام:
403 Forbidden
للتعبير عن أن المستخدم معروف لكن غير مسموح له بتنفيذ الطلب.
دعم Web وAPI داخل نفس Middleware
if ($user && $user->isBanned()) {
$bannedUntil =
$user->banned_until;
if ($request->expectsJson()) {
return response()->json([
'message' =>
'Your account has been suspended.',
'banned_until' =>
$bannedUntil,
], 403);
}
Auth::logout();
$request
->session()
->invalidate();
$request
->session()
->regenerateToken();
return redirect()
->route('login')
->with(
'message',
'تم تعليق حسابك حتى '
.$bannedUntil
->format('Y-m-d H:i:s')
);
}
أخطاء شائعة عند بناء نظام الحظر
الخطأ الأول: استخدام banned_unitl
الاسم الصحيح:
banned_until
وليس:
banned_unitl
الخطأ الثاني: استخدام $dates في Laravel الحديث
بدل:
protected $dates = [
'banned_until'
];
استخدم:
protected function casts(): array
{
return [
'banned_until' => 'datetime',
];
}
الخطأ الثالث: الاعتماد على فحص الحظر وقت Login فقط
هذا لا يغطي حالة قيام المدير بحظر مستخدم يمتلك Session نشطة.
الحل:
Login Check
+
Middleware Check
الخطأ الرابع: تسجيل الخروج دون إبطال Session
الأفضل:
Auth::logout();
$request
->session()
->invalidate();
$request
->session()
->regenerateToken();
الخطأ الخامس: السماح للمستخدم بتغيير banned_until
لا تستخدم مدخلات المستخدم مباشرة:
User::update(
$request->all()
);
خصوصًا إذا كانت هناك حقول إدارية حساسة.
الخطأ السادس: عدم إضافة Authorization إلى لوحة الإدارة
وجود Route مثل:
/admin/users/10/ban
لا يعني أنها آمنة.
يجب استخدام Authorization مناسب مثل:
Policy
Gate
Role / Permission Middleware
النسخة النهائية المختصرة للنظام
Migration
$table->timestamp('banned_until')
->nullable();
User Model
protected function casts(): array
{
return [
'banned_until' => 'datetime',
];
}
public function isBanned(): bool
{
return $this->banned_until !== null
&& $this->banned_until->isFuture();
}
Middleware
public function handle(
Request $request,
Closure $next
): Response {
$user = $request->user();
if ($user && $user->isBanned()) {
$until = $user->banned_until;
Auth::logout();
$request
->session()
->invalidate();
$request
->session()
->regenerateToken();
return redirect()
->route('login')
->with(
'message',
'تم تعليق حسابك حتى '
.$until->format(
'Y-m-d H:i:s'
)
);
}
return $next($request);
}
Routes
Route::middleware([
'auth',
'not-banned',
])->group(function () {
Route::get(
'/dashboard',
DashboardController::class
)->name('dashboard');
});
حظر المستخدم
$user->banned_until =
now()->addDays(7);
$user->save();
فك الحظر
$user->banned_until = null;
$user->save();
الخلاصة
يمكن بناء نظام حظر مؤقت للمستخدمين في Laravel بطريقة بسيطة وفعالة عن طريق إضافة حقل:
banned_until
إلى جدول المستخدمين ثم تحويله إلى:
datetime
داخل User Model.
بعد ذلك نستخدم Middleware للتحقق من:
$user->banned_until->isFuture()
فإذا كان التاريخ ما زال في المستقبل، يتم منع المستخدم من متابعة الطلب وتسجيل خروجه.
أما إذا:
banned_until = null
أو:
banned_until <= now()
فيسمح Laravel له باستخدام التطبيق بصورة طبيعية.
التصميم الأساسي يصبح:
Users Table
│
│
└── banned_until
│
▼
User Model
│
▼
isBanned()
│
▼
CheckBanned Middleware
│
┌─────┴─────┐
│ │
Banned Allowed
│ │
Logout Continue
في المشاريع البسيطة يكفيbanned_untilمع Middleware. أما في المشاريع الأكبر التي تحتاج إلى أسباب الحظر، وسجل كامل للعمليات، ومعرفة المدير الذي نفذ الحظر، فمن الأفضل الانتقال إلى تصميم مستقل مثلuser_bansللاحتفاظ بتاريخ كامل لجميع عمليات الحظر.
السلام عليكم ممكن التحدث عن موضوع (role,permission)authorization بشكل موسع لانها مشكلة يواجهها الكثير بسبب عدم الفهم الصحيح اذا اممكن ولك الشكر الموصول لما تقدمه وسوف تقدمه
عمل ممتاز
very useful
لم يتم اضافة middleware في الروات ولا في الدالة .. يبدوا انك نسيتها .. لكن المقال جميل جدا ومفيد