تطوير الويب

الاستيثاق بـ Laravel Sanctum: tokens API وSPA

5 دقائق للقراءة

📍 الدليل المرجعي: Laravel 11 وPHP 8.4

الاستيثاق هو طبقة الأمن الأكثر حرجًا في أيّ تطبيق ويب. Laravel 11 يعرض Laravel Sanctum للـ APIs والـ SPAs — حلّ خفيف يُصدر tokens API محمولة أو يستند إلى cookies الجلسة. يُغطّي هذا الدليل وضعَي Sanctum: tokens API للعملاء المحمولين والخدمات الخارجية، والاستيثاق SPA لواجهات Vue.js وReact وNuxt على نفس النطاق.

المتطلّبات

  • مشروع Laravel 11.
  • API REST في مكانه.
  • جدول users بـ migrations الافتراضية.
  • الوقت: 45 دقيقة.

الخطوة 1 — تثبيت Sanctum وتحضير القاعدة

في Laravel 11، Sanctum يُثَبَّت عبر install:api الذي يُهَيِّئ routing API في الوقت ذاته.

php artisan install:api

هذا الأمر يُثَبِّت laravel/sanctum، ينشر config/sanctum.php، ينشر migration التي تُنشئ جدول personal_access_tokens. هذا الجدول يُخزّن كلّ tokens المُصدَرة: اسمها، hash SHA-256 لقيمتها، abilities المُرافقة، وتاريخ انتهاء اختياري.

php artisan migrate:status

الخطوة 2 — تهيئة نموذج User بـ HasApiTokens

<?php
namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    use HasApiTokens, HasFactory, Notifiable;

    protected $fillable = [
        'name', 'email', 'password',
    ];

    protected $hidden = [
        'password', 'remember_token',
    ];

    protected $casts = [
        'email_verified_at' => 'datetime',
        'password'          => 'hashed', // hash تلقائي (Laravel 10+)
    ];
}

الـ cast 'password' => 'hashed' ميزة مُقَدَّمة في Laravel 10 تُهَشِّر كلمة السرّ تلقائيًّا عند الإسناد — لا حاجة لـ bcrypt() أو Hash::make() يدوي.

الخطوة 3 — إنشاء controller الاستيثاق

php artisan make:controller Auth/AuthController
<?php
namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;

class AuthController extends Controller
{
    // POST /api/register
    public function register(Request $request): JsonResponse
    {
        $validated = $request->validate([
            'name'     => ['required', 'string', 'max:255'],
            'email'    => ['required', 'email', 'unique:users,email'],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
        ]);

        $user = User::create($validated);
        $token = $user->createToken('api-token')->plainTextToken;

        return response()->json([
            'user'  => $user,
            'token' => $token,
        ], 201);
    }

    // POST /api/login
    public function login(Request $request): JsonResponse
    {
        $request->validate([
            'email'       => ['required', 'email'],
            'password'    => ['required', 'string'],
            'device_name' => ['required', 'string', 'max:100'],
        ]);

        if (! Auth::attempt($request->only('email', 'password'))) {
            throw ValidationException::withMessages([
                'email' => ['Les identifiants fournis sont incorrects.'],
            ]);
        }

        $user  = Auth::user();
        $token = $user->createToken($request->device_name)->plainTextToken;

        return response()->json(['token' => $token]);
    }

    // POST /api/logout
    public function logout(Request $request): JsonResponse
    {
        $request->user()->currentAccessToken()->delete();
        return response()->json(['message' => 'Déconnecté avec succès.']);
    }
}

device_name هي ممارسة Sanctum الجيّدة لتسمية tokens حسب الجهاز (« iPhone 15 لـ Salim »، « Chrome Windows »). تُتيح للمستخدم إلغاء tokens حسب الجهاز.

الخطوة 4 — إعلان routes الاستيثاق

<?php
// routes/api.php

use App\Http\Controllers\Auth\AuthController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

// Routes عامّة
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login',    [AuthController::class, 'login']);

// Routes محمية
Route::middleware('auth:sanctum')->group(function () {
    Route::post('/logout', [AuthController::class, 'logout']);
    Route::get('/user', fn (Request $request) => $request->user());
});

الخطوة 5 — اختبار endpoints الاستيثاق

# 1. التسجيل
curl -s -X POST http://localhost:8000/api/register \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{"name":"Salim Al-Otaibi","email":"salim@example.com","password":"secret12","password_confirmation":"secret12"}' | jq .

# المتوقّع: {"user":{...}, "token":"1|AbCdEfGhIj..."}

# 2. الاتّصال واسترداد token
TOKEN=$(curl -s -X POST http://localhost:8000/api/login \
  -H "Content-Type: application/json" -H "Accept: application/json" \
  -d '{"email":"salim@example.com","password":"secret12","device_name":"curl-test"}' | jq -r '.token')

echo "Token: $TOKEN"

# 3. الوصول إلى route محمية
curl -s http://localhost:8000/api/user \
  -H "Authorization: Bearer $TOKEN" -H "Accept: application/json" | jq .

إشارة النجاح: الأمر الثالث يُرجع JSON للمستخدم المُتَّصَل. إن حصلت على {"message":"Unauthenticated."}، تحقّق من إرسال header Authorization: Bearer TOKEN ومن تطبيق middleware auth:sanctum.

الخطوة 6 — Token abilities: صلاحيات دقيقة

<?php
// إصدار token مع abilities محدّدة
$token = $user->createToken('mobile-app', ['articles:read', 'articles:create'])->plainTextToken;

// التحقّق من abilities في controller أو middleware
public function store(Request $request): JsonResponse
{
    if (! $request->user()->tokenCan('articles:create')) {
        return response()->json(['error' => 'Permission insuffisante.'], 403);
    }
    // ...
}

// إصدار token مع انتهاء (مثلًا 24 ساعة)
$token = $user->createToken(
    'temp-access',
    ['reports:read'],
    now()->addHours(24)
)->plainTextToken;

// جَدوَلَة حذف tokens المنتهية (routes/console.php)
Schedule::command('sanctum:prune-expired --hours=24')->daily();

الأمر sanctum:prune-expired يحذف من personal_access_tokens tokens المنتهية. دون هذا الأمر المُجَدوَل، الجدول ينمو بلا حدود.

أخطاء شائعة

الخطأ السبب الحلّ
Unauthenticated. رغم token صحيح header Accept: application/json غائب أرسل دائمًا Accept: application/json
Token موجود لكن abilities خاطئة Token مُنشأ دون abilities استعمل tokenCan() وتحقّق من abilities عند الإصدار
جدول personal_access_tokens غائب install:api لم يُنَفَّذ شَغِّل php artisan migrate
كلمة السرّ غير مُهَشَّرة cast 'password' => 'hashed' غائب أضف cast أو استعمل Hash::make()

FAQ

الفرق بين Sanctum وPassport؟ Passport يُنَفِّذ بروتوكول OAuth2 الكامل (authorization code، client credentials) — لتكاملات معقّدة. Sanctum أبسط ويُغَطّي 90% من الاحتياجات: tokens API وSPA على نفس النطاق.

كيف نُلغي كلّ tokens مستخدم؟ $user->tokens()->delete(). لإلغاء token الحالي فقط: $request->user()->currentAccessToken()->delete().

أيمكن تحديد عدد tokens نشطة لكلّ مستخدم؟ ليس أصليًّا. يجب تنفيذ المنطق يدويًّا: قبل إصدار token جديد، عُدّ الموجودة واحذف الأقدم إن تجاوزت الحدّ.

الأدلّة الأخرى

🔝 العودة للدليل الرئيسي

Sponsoriser ce contenu

Cet emplacement est à vous

Position premium en fin d'article — c'est l'instant où les lecteurs sont le plus engagés. Réservez cet espace pour votre marque, votre formation ou votre offre.

Recevoir nos tarifs
Publicité