JOURNAL INDEX
2026.09.17/5 VIEWS/5 MIN READ

بناء واجهة REST API آمنة في Laravel باستخدام Sanctum

عبدالرحمن ربيع
عبدالرحمن ربيع Software Engineer & AI Builder

دليل عملي لبناء REST API آمنة باستخدام Laravel Sanctum، يشمل التوكنات والسياسات والتحقق من المدخلات وتحديد الطلبات واختبارات الأمان.

بناء واجهة REST API آمنة في Laravel باستخدام Sanctum

ماذا سنبني، وما المقصود بواجهة آمنة؟

بناء واجهة REST API آمنة في Laravel باستخدام Sanctum لا يقتصر على إضافة توكن إلى ترويسة الطلب. الأمان الحقيقي سلسلة مترابطة تشمل إثبات هوية المستخدم، وتحديد ما يستطيع فعله، والتحقق من البيانات، ومنع الوصول إلى موارد الآخرين. سنطبّق هذه المبادئ على واجهة لإدارة المهام؛ يستطيع المستخدم تسجيل الدخول، وإنشاء مهامه وعرضها وحذفها، دون الاطلاع على مهام حساب آخر.

يفترض الدليل مشروع Laravel حديثًا ببنية Laravel 11 أو أحدث، مع قاعدة بيانات مضبوطة وحساب مستخدم موجود مسبقًا. سنستخدم توكنات الوصول الشخصية المناسبة لتطبيق جوال أو عميل مستقل. أما واجهة المتصفح التابعة لتطبيقك، فغالبًا يكون توثيق Sanctum المعتمد على جلسات الارتباط وملفات تعريف الارتباط أنسب لها. لا تخلط بين المسارين؛ فلكل منهما إعدادات مختلفة لحماية الطلبات وتخزين بيانات الاعتماد.

1. تثبيت Sanctum وتجهيز مسارات API

ابدأ بإنشاء المشروع، ثم اضبط اتصال قاعدة البيانات داخل ملف البيئة قبل تنفيذ الترحيلات. في المشاريع الحديثة يجهّز أمر install:api دعم واجهة API ويثبّت Sanctum، لذلك لا تحتاج إلى تكرار تثبيت الحزمة يدويًا. إن كان مشروعك قائمًا ومجهّزًا بالفعل، افحص ملف المسارات والترحيلات قبل إعادة تنفيذ خطوات التهيئة.

composer create-project laravel/laravel secure-api
cd secure-api
php artisan install:api
php artisan migrate
php artisan make:model Task -m
php artisan make:controller Api/AuthController
php artisan make:controller Api/TaskController
php artisan make:policy TaskPolicy --model=Task
php artisan make:test TaskSecurityTest

تأكّد من احتواء نموذج User على سمة HasApiTokens؛ فهي التي تضيف إنشاء التوكنات وعلاقتها بالمستخدم. احتفظ ببقية السمات والإعدادات الموجودة في النموذج، ولا تستبدل الملف كاملًا بالمقتطف التالي. كذلك يجب أن تكون كلمات المرور الموجودة في قاعدة البيانات مجزّأة، لا محفوظة كنصوص صريحة.

use Laravel\Sanctum\HasApiTokens;

// داخل App\Models\User:
use HasApiTokens;

2. تصميم المهام مع ملكية واضحة

كل مهمة ترتبط بمستخدم واحد عبر user_id. هذا العمود ليس تفصيلًا تنظيميًا فقط؛ بل هو أساس عزل البيانات. عدّل دالة up في ترحيل المهام بالكود التالي، واترك دالة down تحذف جدول tasks. يساعد المفتاح الأجنبي على الحفاظ على سلامة العلاقات، بينما يجعل الفهرس جلب مهام المستخدم المرتبة بالمعرّف أكثر ملاءمة مع نمو البيانات.

Schema::create('tasks', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained()->cascadeOnDelete();
    $table->string('title', 200);
    $table->text('description')->nullable();
    $table->timestamps();
    $table->index(['user_id', 'id']);
});

عرّف الحقول القابلة للإسناد الجماعي داخل Task، ثم أضف علاقة المهام داخل User. لا تضع user_id ضمن الحقول المسموحة؛ سنحدّد المالك من المستخدم الموثّق نفسه. بهذه الطريقة لا يستطيع العميل إنشاء مهمة باسم مستخدم آخر عبر إرسال معرّف إضافي ضمن JSON.

// داخل App\Models\Task:
protected $fillable = ['title', 'description'];

// داخل App\Models\User:
public function tasks(): \Illuminate\Database\Eloquent\Relations\HasMany
{
    return $this->hasMany(\App\Models\Task::class);
}
php artisan migrate

3. تسجيل الدخول وإصدار توكن محدود العمر

نقطة تسجيل الدخول تستقبل البريد وكلمة المرور واسم الجهاز، وتتحقق من المدخلات قبل البحث عن الحساب. استخدم رسالة موحّدة عند فشل الدخول، حتى لا تكشف مباشرةً ما إذا كان البريد مسجّلًا. التوكن التالي صالح لثماني ساعات ويحمل قدرتين محددتين. هذه القدرات ليست أدوارًا إدارية؛ إنها حدود إضافية للتوكن، ولا تُغني عن التحقق من ملكية كل مهمة.

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;

class AuthController extends Controller
{
    public function login(Request $request)
    {
        $data = $request->validate([
            'email' => ['required', 'email', 'max:255'],
            'password' => ['required', 'string', 'max:1024'],
            'device_name' => ['required', 'string', 'max:100'],
        ]);

        $user = User::where('email', $data['email'])->first();

        if (! $user || ! Hash::check($data['password'], $user->password)) {
            return response()->json([
                'message' => 'بيانات الدخول غير صحيحة.',
            ], 401);
        }

        $token = $user->createToken(
            $data['device_name'],
            ['tasks:read', 'tasks:write'],
            now()->addHours(8)
        );

        return response()->json([
            'token_type' => 'Bearer',
            'access_token' => $token->plainTextToken,
            'expires_at' => $token->accessToken->expires_at,
        ])->header('Cache-Control', 'no-store');
    }

    public function logout(Request $request)
    {
        $request->user()->currentAccessToken()->delete();

        return response()->noContent();
    }
}

يخزّن Sanctum تجزئة التوكن في قاعدة البيانات، ويعيد قيمته الصريحة عند الإنشاء فقط. لا تسجّل هذه القيمة في أدوات المراقبة أو سجلات الطلبات. ودالة الخروج هنا مصمّمة لمسار توثيق Bearer الذي نبنيه؛ أما الخروج من جلسة متصفح فيحتاج إبطال الجلسة وتجديد توكن CSRF بدل حذف توكن شخصي.

4. ضبط الصلاحيات ومحدّد معدل الطلبات

سجّل الاسم المختصر لوسيط قدرات Sanctum داخل استدعاء withMiddleware الموجود في bootstrap/app.php. لا تضف سلسلة إعداد ثانية أو تحذف إعدادات المشروع الأخرى. يتحقق وسيط abilities من امتلاك التوكن لجميع القدرات المذكورة، بينما يتحقق auth:sanctum أولًا من هوية صاحب الطلب.

->withMiddleware(function (\Illuminate\Foundation\Configuration\Middleware $middleware) {
    $middleware->alias([
        'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class,
    ]);
})

أضف محدّدَي الطلبات التاليين إلى دالة boot في AppServiceProvider. تقييد الدخول بحسب عنوان الشبكة، وبحسب اجتماع البريد والعنوان، يخفّض فرص التخمين المتكرر، لكنه ليس بديلًا عن المراقبة أو المصادقة متعددة العوامل. وفي بيئة متعددة الخوادم استخدم مخزنًا مشتركًا للكاش، واضبط الوكلاء الموثوقين كي لا تعتمد القيود على عنوان شبكة غير صحيح.

use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;

// داخل boot:
RateLimiter::for('login', function (Request $request) {
    $identity = hash('sha256', strtolower((string) $request->input('email')));

    return [
        Limit::perMinute(20)->by('login-ip:'.$request->ip()),
        Limit::perMinute(5)->by('login-user:'.$identity.'|'.$request->ip()),
    ];
});

RateLimiter::for('api', function (Request $request) {
    return Limit::perMinute(60)->by(
        'api:'.($request->user()?->id ?? $request->ip())
    );
});

5. تعريف المسارات المحمية

ضع المسارات في routes/api.php؛ يضيف Laravel بادئة /api تلقائيًا في الإعداد المعتاد. نفصل القراءة عن الكتابة لتوضيح معنى قدرات التوكن. أي طلب لإنشاء مهمة يحتاج هوية صالحة وقدرة كتابة، لكن ذلك لا يمنحه حق حذف مهمة ليست له. أرسل دائمًا Accept: application/json للحصول على استجابات أخطاء ملائمة للعميل بدل سلوك صفحات الويب.

use App\Http\Controllers\Api\AuthController;
use App\Http\Controllers\Api\TaskController;
use Illuminate\Support\Facades\Route;

Route::post('/login', [AuthController::class, 'login'])
    ->middleware('throttle:login');

Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () {
    Route::post('/logout', [AuthController::class, 'logout']);

    Route::middleware('abilities:tasks:read')->group(function () {
        Route::get('/tasks', [TaskController::class, 'index']);
        Route::get('/tasks/{task}', [TaskController::class, 'show']);
    });

    Route::middleware('abilities:tasks:write')->group(function () {
        Route::post('/tasks', [TaskController::class, 'store']);
        Route::delete('/tasks/{task}', [TaskController::class, 'destroy']);
    });
});

6. منع الوصول إلى مهام المستخدمين الآخرين

من أخطر أخطاء API قبول معرّف مورد ثم إعادته دون فحص مالكه. معرفة المستخدم للرقم الصحيح لا تعني امتلاكه صلاحية الوصول. عدّل TaskPolicy لتقارن مالك المهمة بالمستخدم الحالي. ولجعل التسجيل واضحًا، أضف ربط السياسة التالي إلى boot في AppServiceProvider، حتى لو كان الاكتشاف التلقائي للسياسات يعمل وفق أسماء المشروع التقليدية.

namespace App\Policies;

use App\Models\Task;
use App\Models\User;

class TaskPolicy
{
    public function view(User $user, Task $task): bool
    {
        return $user->id === $task->user_id;
    }

    public function delete(User $user, Task $task): bool
    {
        return $user->id === $task->user_id;
    }
}
// داخل boot في AppServiceProvider:
\Illuminate\Support\Facades\Gate::policy(
    \App\Models\Task::class,
    \App\Policies\TaskPolicy::class
);

قائمة المهام تحتاج عزلًا داخل الاستعلام نفسه، وليس فحصًا بعد تحميل جميع السجلات. أما العرض والحذف فيستخدمان السياسة قبل قراءة المورد أو تغييره. ستعيد السياسة هنا 403 للموارد غير المسموح بها؛ ويمكن تصميم استجابة 404 بدلًا منها عندما تتطلب سياسة المنتج إخفاء وجود المورد.

7. التحقق من المدخلات وتنفيذ العمليات

يقبل المتحكم عنوانًا ووصفًا فقط، ويرفض إرسال user_id صراحةً. نستخدم البيانات الناتجة عن validate بدل تمرير جميع مدخلات الطلب إلى النموذج. كذلك نحدّد أعمدة الاستجابة لتقليل كشف الحقول غير الضرورية، ونستخدم ترقيم الصفحات بدل تحميل القائمة كاملة. عند توسع الواجهة، انقل التحقق إلى Form Requests والتنسيق إلى API Resources لتوحيد العقود بين المسارات.

namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Models\Task;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;

class TaskController extends Controller
{
    public function index(Request $request)
    {
        return $request->user()->tasks()
            ->select(['id', 'title', 'description', 'created_at'])
            ->orderByDesc('id')
            ->paginate(20);
    }

    public function store(Request $request)
    {
        $data = $request->validate([
            'title' => ['required', 'string', 'max:200'],
            'description' => ['nullable', 'string', 'max:5000'],
            'user_id' => ['prohibited'],
        ]);

        $task = $request->user()->tasks()->create($data);

        return response()->json([
            'data' => $task->only(['id', 'title', 'description', 'created_at']),
        ], 201);
    }

    public function show(Task $task)
    {
        Gate::authorize('view', $task);

        return response()->json([
            'data' => $task->only(['id', 'title', 'description', 'created_at']),
        ]);
    }

    public function destroy(Task $task)
    {
        Gate::authorize('delete', $task);
        $task->delete();

        return response()->noContent();
    }
}

تُعيد أخطاء التحقق الحالة 422، والإنشاء الناجح 201، والحذف 204 دون جسم استجابة. وتذكّر أن قبول وصف نصي لا يجعله آمنًا للعرض كـHTML؛ على الواجهة الأمامية ترميز النص عند عرضه، أو تطبيق تنقية مناسبة إذا كان المنتج يسمح فعلًا بمحتوى منسّق.

8. تجربة التوكن عبر طلبات حقيقية

شغّل الخادم المحلي، ثم سجّل الدخول باستخدام حسابك الموجود. انسخ access_token إلى متغير TOKEN داخل بيئة اختبار موثوقة، ولا تحفظه في ملفات مشتركة أو مستودع Git. استخدام HTTP هنا للتطوير المحلي فقط؛ يجب أن تنتقل بيانات الاعتماد والتوكنات عبر HTTPS في الإنتاج.

php artisan serve
curl -X POST http://127.0.0.1:8000/api/login \
  -H 'Accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{"email":"developer@example.com","password":"your-password","device_name":"local-cli"}'

TOKEN='paste-access-token-here'

curl -X POST http://127.0.0.1:8000/api/tasks \
  -H 'Accept: application/json' \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"title":"مراجعة صلاحيات API","description":"اختبار عزل المهام"}'

curl http://127.0.0.1:8000/api/tasks \
  -H 'Accept: application/json' \
  -H "Authorization: Bearer $TOKEN"

curl -X POST http://127.0.0.1:8000/api/logout \
  -H 'Accept: application/json' \
  -H "Authorization: Bearer $TOKEN"

9. اختبار الأمان قبل النشر

اختبار نجاح إنشاء مهمة وحده لا يكفي. اختبر الحدود التي يفترض ألا يتجاوزها العميل: طلب بلا توثيق، وتوكن قراءة يحاول الكتابة، ومستخدم يحاول فتح مهمة غيره. المثال التالي يستخدم PHPUnit ومصادقة Sanctum المساعدة، ولذلك يختبر السياسات والقدرات، لكنه لا يختبر صلاحية توكن حقيقي أو انتهاءه؛ أضف اختبارات مستقلة لذلك باستخدام ترويسة Bearer.

namespace Tests\Feature;

use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Laravel\Sanctum\Sanctum;
use Tests\TestCase;

class TaskSecurityTest extends TestCase
{
    use RefreshDatabase;

    public function test_guests_cannot_list_tasks(): void
    {
        $this->getJson('/api/tasks')->assertUnauthorized();
    }

    public function test_user_cannot_read_another_users_task(): void
    {
        $owner = User::factory()->create();
        $other = User::factory()->create();
        $task = $owner->tasks()->create(['title' => 'Private']);

        Sanctum::actingAs($other, ['tasks:read']);

        $this->getJson('/api/tasks/'.$task->id)->assertForbidden();
    }

    public function test_read_only_token_cannot_create_tasks(): void
    {
        Sanctum::actingAs(User::factory()->create(), ['tasks:read']);

        $this->postJson('/api/tasks', ['title' => 'Blocked'])
            ->assertForbidden();
    }
}
php artisan test --filter=TaskSecurityTest

10. قائمة مراجعة الإنتاج وإدارة دورة التوكن

قبل تشغيل الاختبارات، اضبط قاعدة بيانات اختبار منفصلة؛ سمة RefreshDatabase تعيد تهيئة البيانات في البيئة المستهدفة. وبعد نجاحها، راجع إعدادات الإنتاج ودورة حياة التوكنات. انتهاء التوكن يمنع استعماله، لكنه لا يعني حذف سجله تلقائيًا. جدولة التنظيف تقلّل تراكم السجلات، بينما يجب أن توفّر للمستخدم وسيلة لإلغاء أجهزة قديمة أو مشبوهة.

  • فعّل HTTPS واضبط APP_DEBUG=false، ولا تعرض تفاصيل الاستثناءات للعميل.
  • اسمح في CORS بالأصول المطلوبة فقط؛ CORS ليس بديلًا عن التوثيق والصلاحيات.
  • احجب كلمات المرور وترويسة Authorization عن السجلات وتقارير الأخطاء.
  • خزّن توكنات الجوال في التخزين الآمن للنظام، وتجنّب تخزين توكن طويل العمر في localStorage.
  • راقب ارتفاع أخطاء 401 و403 و429، وحدّث الاعتماديات بانتظام.
  • ألغِ جميع توكنات الحساب عند الحاجة الأمنية باستخدام علاقة tokens، وفق سياسة واضحة.
// داخل routes/console.php:
use Illuminate\Support\Facades\Schedule;

Schedule::command('sanctum:prune-expired --hours=24')->daily();

// لإلغاء جميع توكنات مستخدم محدد:
// $user->tokens()->delete();

تحتاج الجدولة إلى تشغيل مجدول Laravel على الخادم، مثل استدعاء schedule:run كل دقيقة. بهذا التصميم تصبح Sanctum طبقة إثبات الهوية، والقدرات حدودًا للتوكن، والسياسات حاجزًا حول الموارد، والاختبارات ضمانًا ضد التراجع. هذه الطبقات مجتمعة هي الأساس العملي لواجهة Laravel REST API قابلة للتطوير وأكثر أمانًا.

SHARE شارك المقال
عبدالرحمن ربيع
كتبه

عبدالرحمن ربيع

Software Engineer & AI Builder

مطور برمجيات متكامل ومصمم جرافيك مع أكثر من 4 سنوات خبرة في بناء تطبيقات الويب الحديثة باستخدام PHP و JavaScript و HTML و CSS. خلفية قوية في تصميم UI/UX واستخدام متقدم لأدوات الذكاء الاصطناعي لتعزيز كفاءة التطوير والأتمتة واتخاذ القرارات. حاصل على ماجستير تنفي...

COMMENTS

التعليقات (0)

كن أول من يعلّق على هذا المقال.

أضف تعليقك

يظهر تعليقك بعد المراجعة.