Laravel Sanctum: الدليل الشامل للمصادقة وحماية REST APIs في Laravel
تُعد المصادقة Authentication واحدة من أهم الأجزاء في أي تطبيق ويب أو تطبيق هاتف يتعامل مع حسابات المستخدمين. فعند بناء REST API نحتاج إلى وسيلة آمنة تمكّن الخادم من معرفة هوية المستخدم، وتحديد ما إذا كان يملك الصلاحية للوصول إلى مورد أو تنفيذ عملية معينة.
يوفر Laravel عدة حلول للمصادقة، ومن أبسطها وأكثرها ملاءمةً لتطبيقات الهاتف وواجهات API البسيطة Laravel Sanctum.
في هذا الدليل سنتعرف على Laravel Sanctum بصورة عملية، بدءًا من فهم آلية عمله، مرورًا بتثبيته وإصدار Access Tokens وحماية API Routes، وصولًا إلى Token Abilities وإلغاء الرموز وانتهاء صلاحيتها.
جدول المحتويات
- ما هو Laravel Sanctum؟
- كيف تعمل المصادقة في Sanctum؟
- SPA Authentication أم API Tokens؟
- تثبيت Laravel Sanctum
- تجهيز User Model
- إنشاء مستخدم تجريبي
- تسجيل مستخدم جديد
- تسجيل الدخول
- استخدام Device Name
- إرسال Access Token
- حماية API Routes
- الحصول على المستخدم الحالي
- تسجيل الخروج من الجهاز الحالي
- تسجيل الخروج من جميع الأجهزة
- Token Abilities والصلاحيات
- حماية Routes باستخدام Abilities
- انتهاء صلاحية Access Tokens
- تحديد Expiration لكل Token
- حذف Tokens المنتهية
- اختبار API باستخدام Postman
- HTTP Status Codes
- مثال كامل لـ routes/api.php
- مثال AuthController متكامل
- اعتبارات أمنية مهمة
- Sanctum مع SPA
- هل Sanctum بديل عن OAuth؟
- الخلاصة
ما هو Laravel Sanctum؟
Laravel Sanctum هو نظام مصادقة خفيف توفره Laravel للتعامل مع مجموعة من سيناريوهات المصادقة، ومن أبرزها:
- تطبيقات الهاتف Mobile Applications.
- واجهات REST APIs.
- تطبيقات Single Page Applications أو SPA.
- إصدار Personal Access Tokens.
- إنشاء أكثر من Token للمستخدم نفسه.
- تحديد صلاحيات مختلفة لكل Token.
الفكرة الأساسية في Token Authentication بسيطة. بعد نجاح تسجيل الدخول يستطيع الخادم إنشاء Access Token للمستخدم، ثم يقوم التطبيق بإرسال هذا Token مع الطلبات التي تحتاج إلى مصادقة.
على سبيل المثال:
Authorization: Bearer YOUR_ACCESS_TOKENيستقبل Laravel الطلب ويتحقق من Token، ومن خلاله يستطيع تحديد المستخدم المرتبط بالطلب.
لكن هناك نقطة مهمة يجب فهمها قبل البدء: Sanctum لا يستخدم الطريقة نفسها في جميع سيناريوهات المصادقة.
كيف تعمل المصادقة في Laravel Sanctum؟
يوفر Sanctum بشكل أساسي طريقتين للمصادقة:
API Token Authentication
تُستخدم هذه الطريقة عادةً مع تطبيقات الهاتف أو التطبيقات الخارجية التي تتصل بـ Laravel API.
Flutter / Android / iOS
|
| Bearer Token
v
Laravel API
|
v
Databaseبعد تسجيل الدخول يحصل التطبيق على Token ويرسله في الطلبات التالية.
SPA Authentication
إذا كان لديك تطبيق Frontend خاص بك مثل React أو Vue ويتصل بـ Laravel ضمن بنية First-Party، فإن Sanctum يستطيع استخدام نظام Session وCookies الخاص بـ Laravel مع حماية CSRF بدل الاعتماد على Personal Access Tokens.
وهذا الفرق أساسي لفهم الطريقة الصحيحة لاستخدام Sanctum.
متى نستخدم SPA Authentication ومتى نستخدم API Tokens؟
إذا كان لدينا تطبيق هاتف:
Flutter / Android / iOS
|
v
Laravel APIفيمكن استخدام:
Authorization: Bearer TOKENأما إذا كان لدينا تطبيق Frontend تابع للنظام نفسه مثل React أو Vue ويعمل مع Laravel كتطبيق First-Party، فمن الأفضل دراسة استخدام SPA Authentication الخاص بـ Sanctum بدل تخزين Personal Access Token في JavaScript.
في هذا المقال سنركز بشكل أساسي على API Token Authentication لأنها من أكثر الطرق استخدامًا عند بناء REST API أو Backend لتطبيق هاتف.
تثبيت Laravel Sanctum
في إصدارات Laravel الحديثة يمكن تجهيز API Authentication باستخدام الأمر:
php artisan install:apiبعد ذلك يتم تنفيذ migrations عند الحاجة:
php artisan migrateيستخدم Sanctum جدولًا باسم:
personal_access_tokensويُستخدم هذا الجدول لتخزين المعلومات المرتبطة بالـAccess Tokens.
من المهم معرفة أن Sanctum لا يخزن القيمة الأصلية الكاملة للـToken بشكل مكشوف داخل قاعدة البيانات، وإنما يخزن Hash للرمز.
تجهيز User Model
لكي يستطيع المستخدم إنشاء Access Tokens يجب أن يستخدم User Model الـTrait التالية:
use Laravel\Sanctum\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;
}توفر HasApiTokens مجموعة من الوظائف المهمة للتعامل مع Tokens.
على سبيل المثال:
$user->createToken(...);كما يمكن الوصول إلى Tokens الخاصة بالمستخدم:
$user->tokens;إنشاء مستخدم تجريبي
يمكن استخدام Seeder لإنشاء مستخدم للاختبار:
php artisan make:seeder UsersTableSeederثم:
<?php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Hash;
class UsersTableSeeder extends Seeder
{
public function run(): void
{
User::create([
'name' => 'John Doe',
'email' => 'john@example.com',
'password' => Hash::make('password123'),
]);
}
}بعد ذلك يمكن تشغيل Seeder:
php artisan db:seed --class=UsersTableSeederلاحظ استخدام:
Hash::make()وذلك لتخزين كلمة المرور بصورة آمنة بدل تخزينها كنص صريح Plain Text.
تسجيل مستخدم جديد
لننشئ Auth Controller:
php artisan make:controller Api/AuthControllerثم نضيف Route داخل:
routes/api.phpبالشكل التالي:
use App\Http\Controllers\Api\AuthController;
use Illuminate\Support\Facades\Route;
Route::post('/register', [AuthController::class, 'register']);داخل Controller يمكن إنشاء دالة التسجيل:
public function register(Request $request)
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', 'unique:users,email'],
'password' => [
'required',
'confirmed',
Password::min(8),
],
'device_name' => ['required', 'string', 'max:255'],
]);
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
$token = $user->createToken(
$validated['device_name']
)->plainTextToken;
return response()->json([
'message' => 'User registered successfully.',
'user' => $user,
'token' => $token,
], 201);
}وبما أننا استخدمنا قاعدة التحقق:
'confirmed'فيجب إرسال حقل password_confirmation أيضًا:
{
"name": "John Doe",
"email": "john@example.com",
"password": "password123",
"password_confirmation": "password123",
"device_name": "iPhone"
}تسجيل الدخول باستخدام Laravel Sanctum
نضيف Route لتسجيل الدخول:
Route::post('/login', [AuthController::class, 'login']);ثم نضيف الدالة التالية داخل Controller:
public function login(Request $request)
{
$validated = $request->validate([
'email' => ['required', 'email'],
'password' => ['required', 'string'],
'device_name' => ['required', 'string'],
]);
$user = User::where('email', $validated['email'])->first();
if (! $user || ! Hash::check($validated['password'], $user->password)) {
throw ValidationException::withMessages([
'email' => ['The provided credentials are incorrect.'],
]);
}
$token = $user->createToken(
$validated['device_name']
)->plainTextToken;
return response()->json([
'message' => 'Login successful.',
'user' => $user,
'token' => $token,
]);
}في البداية نحصل على المستخدم من خلال البريد الإلكتروني:
$user = User::where('email', $validated['email'])->first();ثم نتحقق من كلمة المرور:
Hash::check($validated['password'], $user->password)إذا كانت بيانات الدخول صحيحة ننشئ Token:
$user->createToken($validated['device_name'])ثم نحصل على القيمة التي سيستخدمها Client:
->plainTextTokenلماذا نستخدم Device Name؟
يمكن أن يمتلك المستخدم أكثر من جهاز، مثل:
- iPhone
- Android
- iPad
- Desktop Application
وبالتالي يمكن إصدار Token مستقل لكل جهاز:
$user->createToken('iPhone');أو:
$user->createToken('Android');وهذا يجعل إدارة الجلسات والأجهزة أكثر مرونة، حيث يمكن إلغاء Token لجهاز معين دون الحاجة إلى تسجيل خروج المستخدم من جميع أجهزته.
إرسال Access Token
بعد تسجيل الدخول قد يعيد API استجابة مشابهة:
{
"message": "Login successful.",
"user": {
"id": 1,
"name": "John Doe",
"email": "john@example.com"
},
"token": "1|xxxxxxxxxxxxxxxxxxxxxxxx"
}القيمة الموجودة في token هي التي يحتاج إليها التطبيق لإرسال الطلبات المحمية.
يتم إرسالها داخل HTTP Header:
Authorization: Bearer 1|xxxxxxxxxxxxxxxxxxxxxxxxويُفضّل كذلك إرسال:
Accept: application/jsonحماية API Routes
لنفترض أن لدينا Route:
Route::get('/profile', [ProfileController::class, 'show']);إذا أردنا منع المستخدم غير المسجل من الوصول إليه نستخدم:
Route::get('/profile', [ProfileController::class, 'show'])
->middleware('auth:sanctum');كما يمكن حماية مجموعة Routes كاملة:
Route::middleware('auth:sanctum')->group(function () {
Route::get('/profile', [ProfileController::class, 'show']);
Route::get('/orders', [OrderController::class, 'index']);
Route::post('/logout', [AuthController::class, 'logout']);
});الآن لا يمكن الوصول إلى هذه المسارات إلا من خلال Request تمت مصادقته بواسطة Sanctum.
الحصول على المستخدم الحالي
بعد نجاح المصادقة يمكن الحصول على المستخدم من Request:
public function profile(Request $request)
{
return response()->json([
'user' => $request->user(),
]);
}يستطيع Laravel تحديد المستخدم لأن Sanctum قام بالتحقق من بيانات المصادقة المرتبطة بالطلب.
تسجيل الخروج من الجهاز الحالي
إذا أردنا تسجيل خروج المستخدم من الجهاز الحالي فقط، يمكن حذف Token المستخدم في الطلب الحالي:
public function logout(Request $request)
{
$request->user()
->currentAccessToken()
->delete();
return response()->json([
'message' => 'Logged out successfully.',
]);
}بمجرد حذف Token لن يستطيع Client استخدامه مرة أخرى للوصول إلى Routes المحمية.
تسجيل الخروج من جميع الأجهزة
هناك فرق بين تسجيل الخروج من الجهاز الحالي وتسجيل الخروج من جميع الأجهزة.
لحذف جميع Tokens الخاصة بالمستخدم:
$request->user()->tokens()->delete();مثال:
public function logoutAll(Request $request)
{
$request->user()->tokens()->delete();
return response()->json([
'message' => 'Logged out from all devices.',
]);
}وبذلك تصبح جميع Access Tokens السابقة الخاصة بالمستخدم غير صالحة.
أما لحذف Token محدد:
$user->tokens()
->where('id', $tokenId)
->delete();هذه الإمكانية مفيدة عند إنشاء قسم لإدارة الأجهزة والجلسات النشطة داخل التطبيق.
Token Abilities والصلاحيات
لا يقتصر Sanctum على إنشاء Tokens فقط، بل يمكن إعطاء Token صلاحيات محددة تسمى Abilities.
لنفترض أننا نريد إنشاء Token يستطيع قراءة الطلبات فقط:
$token = $user->createToken(
'mobile-app',
['orders:read']
)->plainTextToken;ويمكن إنشاء Token يمتلك عدة صلاحيات:
$token = $user->createToken(
'admin-app',
[
'orders:read',
'orders:create',
'orders:update'
]
)->plainTextToken;ثم يمكن التحقق من صلاحية معينة:
if ($request->user()->tokenCan('orders:update')) {
// Token has permission.
}كما يمكن استخدام:
tokenCant()للتحقق من عدم امتلاك Token لصلاحية معينة:
if ($request->user()->tokenCant('orders:update')) {
abort(403);
}حماية Routes باستخدام Token Abilities
يوفر Sanctum Middleware للتحقق من Abilities المطلوبة للوصول إلى Route معين.
يمكن تعريف Middleware Aliases داخل:
bootstrap/app.phpباستخدام:
use Laravel\Sanctum\Http\Middleware\CheckAbilities;
use Laravel\Sanctum\Http\Middleware\CheckForAnyAbility;ثم:
->withMiddleware(function (Middleware $middleware): void {
$middleware->alias([
'abilities' => CheckAbilities::class,
'ability' => CheckForAnyAbility::class,
]);
})بعد ذلك يمكن حماية Route:
Route::get('/orders', [OrderController::class, 'index'])
->middleware([
'auth:sanctum',
'abilities:orders:read'
]);وبذلك لا يكفي أن يكون Token صالحًا، بل يجب أيضًا أن يمتلك الصلاحية المطلوبة.
انتهاء صلاحية Access Tokens
من النقاط المهمة في Sanctum أن Tokens لا تنتهي افتراضيًا لمجرد مرور مدة زمنية ما لم يتم إعداد Expiration أو إلغاء Token.
يمكن تحديد مدة عامة من:
config/sanctum.phpعلى سبيل المثال:
'expiration' => 10080,القيمة تكون بالدقائق، وبالتالي:
60= ساعة.1440= يوم.10080= 7 أيام.
اختيار المدة المناسبة يعتمد على طبيعة التطبيق ومتطلباته الأمنية.
تحديد Expiration لكل Token
يمكن كذلك تحديد تاريخ انتهاء صلاحية Token عند إنشائه:
$token = $user->createToken(
'mobile-app',
['*'],
now()->addWeek()
)->plainTextToken;وبذلك يكون لهذا Token تاريخ انتهاء مستقل.
حذف Tokens المنتهية
مع مرور الوقت قد يحتوي جدول personal_access_tokens على Tokens منتهية الصلاحية.
يوفر Sanctum أمرًا لتنظيفها:
php artisan sanctum:prune-expired --hours=24ويمكن جدولة هذا الأمر ليعمل بصورة دورية بدل تنفيذه يدويًا.
اختبار Laravel Sanctum باستخدام Postman
يمكن تجربة تسجيل الدخول عبر:
POST /api/loginمع Body:
{
"email": "john@example.com",
"password": "password123",
"device_name": "Postman"
}إذا كانت البيانات صحيحة سيعيد API الـToken.
بعد ذلك نختبر:
GET /api/profileونضيف:
Authorization: Bearer YOUR_TOKENفي Postman يمكن أيضًا اختيار Bearer Token من قسم Authorization ووضع Token في الحقل المخصص له.
HTTP Status Codes
عند بناء REST API احترافي يجب الاهتمام باستخدام HTTP Status Codes المناسبة.
200 OK
تُستخدم عادةً عندما تتم العملية بنجاح.
201 Created
تُستخدم عند إنشاء Resource جديد بنجاح، مثل إنشاء مستخدم جديد.
401 Unauthorized
تعني أن الطلب يحتاج إلى Authentication صحيحة.
403 Forbidden
تعني أن هوية المستخدم معروفة، لكنه لا يمتلك الصلاحية المطلوبة لتنفيذ العملية.
422 Unprocessable Content
تُستخدم عادةً عندما توجد مشكلة في البيانات المدخلة أو Validation.
استخدام Status Codes بشكل صحيح يجعل API أسهل في التكامل والصيانة ومعالجة الأخطاء في تطبيقات الهاتف والـFrontend.
مثال كامل لـ routes/api.php
يمكن أن يصبح ملف Routes لدينا بالشكل التالي:
<?php
use App\Http\Controllers\Api\AuthController;
use App\Http\Controllers\Api\OrderController;
use Illuminate\Support\Facades\Route;
Route::post('/register', [AuthController::class, 'register']);
Route::post('/login', [AuthController::class, 'login']);
Route::middleware('auth:sanctum')->group(function () {
Route::get('/profile', [AuthController::class, 'profile']);
Route::get('/orders', [OrderController::class, 'index']);
Route::post('/logout', [AuthController::class, 'logout']);
Route::post('/logout-all', [AuthController::class, 'logoutAll']);
});وبذلك لدينا Public Routes:
/api/register
/api/loginوProtected Routes:
/api/profile
/api/orders
/api/logout
/api/logout-allمثال AuthController متكامل
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rules\Password;
use Illuminate\Validation\ValidationException;
class AuthController extends Controller
{
public function register(Request $request)
{
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => [
'required',
'email',
'max:255',
'unique:users,email'
],
'password' => [
'required',
'confirmed',
Password::min(8),
],
'device_name' => [
'required',
'string',
'max:255'
],
]);
$user = User::create([
'name' => $validated['name'],
'email' => $validated['email'],
'password' => Hash::make($validated['password']),
]);
$token = $user->createToken(
$validated['device_name']
)->plainTextToken;
return response()->json([
'message' => 'User registered successfully.',
'user' => $user,
'token' => $token,
], 201);
}
public function login(Request $request)
{
$validated = $request->validate([
'email' => ['required', 'email'],
'password' => ['required', 'string'],
'device_name' => ['required', 'string'],
]);
$user = User::where(
'email',
$validated['email']
)->first();
if (
! $user ||
! Hash::check(
$validated['password'],
$user->password
)
) {
throw ValidationException::withMessages([
'email' => [
'The provided credentials are incorrect.'
],
]);
}
$token = $user->createToken(
$validated['device_name']
)->plainTextToken;
return response()->json([
'message' => 'Login successful.',
'user' => $user,
'token' => $token,
]);
}
public function profile(Request $request)
{
return response()->json([
'user' => $request->user(),
]);
}
public function logout(Request $request)
{
$request->user()
->currentAccessToken()
->delete();
return response()->json([
'message' => 'Logged out successfully.',
]);
}
public function logoutAll(Request $request)
{
$request->user()
->tokens()
->delete();
return response()->json([
'message' => 'Logged out from all devices.',
]);
}
}اعتبارات أمنية مهمة عند استخدام Laravel Sanctum
استخدام Sanctum لا يعني أن API أصبح آمنًا تلقائيًا. المصادقة ليست سوى طبقة واحدة من منظومة حماية التطبيق.
استخدم HTTPS دائمًا
يجب عدم إرسال Access Tokens عبر اتصال HTTP غير مشفر في بيئة Production.
https://لا تسجل Access Tokens داخل Logs
تجنب تسجيل Token داخل ملفات Log، مثل:
Log::info($token);كما يجب عدم وضع Access Tokens داخل رسائل الأخطاء أو خدمات Analytics.
لا ترسل Token داخل URL
تجنب:
/api/profile?token=xxxxواستخدم HTTP Authorization Header:
Authorization: Bearer TOKENامنح Token أقل قدر ممكن من الصلاحيات
إذا كان التطبيق يحتاج فقط إلى:
orders:readفلا توجد حاجة لمنحه جميع الصلاحيات.
يعرف هذا المبدأ باسم Principle of Least Privilege.
ألغِ Tokens عند الحاجة
عند الاشتباه في جهاز أو Session يجب أن يوفر النظام وسيلة لإلغاء Token الخاص بذلك الجهاز.
حدد سياسة مناسبة لانتهاء Tokens
ترك Token صالحًا إلى أجل غير محدد ليس الخيار الأنسب لجميع التطبيقات، خصوصًا الأنظمة التي تتعامل مع معلومات حساسة.
استخدم Rate Limiting
يجب الاهتمام بحماية Endpoints الحساسة، مثل:
/login/register/password-reset
وذلك لتقليل محاولات الإساءة أو إرسال عدد كبير من الطلبات.
لا تعتمد على Token Abilities وحدها
Abilities مفيدة في تحديد ما يستطيع Token فعله، لكنها لا تلغي الحاجة إلى Authorization داخل التطبيق.
في الأنظمة الكبيرة يمكن استخدام:
- Laravel Policies
- Laravel Gates
- Authorization Rules
على سبيل المثال، امتلاك المستخدم لصلاحية:
orders:updateلا يعني بالضرورة أنه يجب أن يستطيع تعديل أي Order داخل النظام. فقد يكون مسموحًا له فقط بتعديل الطلبات التابعة لحسابه.
استخدام Laravel Sanctum مع SPA
إذا كنت تستخدم Sanctum مع First-Party SPA فإن طريقة المصادقة تختلف عن Mobile API.
في هذا السيناريو يستطيع Sanctum استخدام Session Cookies بدل Personal Access Tokens.
في Laravel الحديث يمكن تفعيل Stateful API Middleware من:
bootstrap/app.phpباستخدام:
->withMiddleware(function (Middleware $middleware): void {
$middleware->statefulApi();
})ويبدأ SPA عادةً بطلب:
/sanctum/csrf-cookieوذلك لتهيئة حماية CSRF قبل عملية تسجيل الدخول.
لذلك لا ينبغي أخذ مثال Bearer Token الخاص بتطبيقات الهاتف وتطبيقه تلقائيًا على First-Party SPA دون فهم الفرق بين الطريقتين.
هل Laravel Sanctum بديل عن OAuth؟
ليس في جميع الحالات.
Sanctum مناسب جدًا لسيناريوهات مثل:
- Laravel مع Flutter.
- Laravel Backend لتطبيق Android أو iOS.
- REST API بسيط.
- First-Party SPA.
- Personal Access Tokens.
أما إذا كان المشروع يحتاج إلى تطبيق كامل لبروتوكول OAuth2 ومتطلباته، فقد يكون من المناسب استخدام حل مثل Laravel Passport أو مزود OAuth/OIDC متخصص وفق بنية ومتطلبات المشروع.
لذلك يجب اختيار Sanctum بناءً على طبيعة النظام وليس فقط بسبب سهولة تركيبه.
الخلاصة
يوفر Laravel Sanctum طريقة بسيطة وقوية لبناء طبقة Authentication لتطبيقات Laravel دون الدخول في تعقيدات OAuth عندما لا يحتاج المشروع إليها.
في تطبيقات Mobile وواجهات API يمكن إصدار Access Token للمستخدم وإرساله في الطلبات باستخدام:
Authorization: Bearer TOKENثم حماية Routes بواسطة:
auth:sanctumكما يمكن إنشاء أكثر من Token للمستخدم، وتحديد Abilities لكل Token، وإلغاء Token الحالي أو جميع Tokens، وتحديد مدة انتهاء للرموز.
أما في تطبيقات SPA التابعة للنظام نفسه، فيوفر Sanctum آلية مختلفة تعتمد على Laravel Sessions وCookies وCSRF بدل الاعتماد على Personal Access Tokens.
والأهم عند بناء نظام حقيقي ألا نتعامل مع Sanctum باعتباره الحماية الكاملة للتطبيق؛ بل باعتباره طبقة Authentication يجب أن تعمل إلى جانب HTTPS وValidation وAuthorization وRate Limiting وسياسات مناسبة لإدارة Tokens والصلاحيات.
بهذا نكون قد وضعنا أساسًا واضحًا وقابلًا للتوسع لبناء نظام مصادقة وحماية REST APIs باستخدام Laravel Sanctum.
مقال رهيب، شكرا يا ايثار ❤️
بستفيد جددا من مقالات حضرتك الله يحفظك يارب , ويتريت تستمر ♥
شككرا على مجهودك لو سمحت لدي استفسار مالفرق بين Sanctum و passport ومتى نستخدم هذه أو تلك؟
اين يتم إضافة هذا الكود $token=$user->createToken(\'my-app-token\',[\'brand-list\'])->plainTextToken;
مقالات جميلة .. شكرا لك..