التعامل مع API في Laravel – الجزء السادس: حماية روابط API باستخدام API Key وMiddleware
في الأجزاء السابقة من سلسلة Laravel API قمنا بإنشاء REST API يستطيع جلب البيانات وإضافتها وتعديلها وحذفها، كما تعرفنا على Versioning وRate Limiting.
لكن هناك سؤال مهم:
هل نريد أن يستطيع أي شخص يعرف رابط API إرسال Requests إليه؟
في بعض الحالات قد نملك API مخصصًا للتواصل بين أنظمة موثوقة، مثل Backend يتواصل مع Backend آخر، ونريد إضافة طبقة بسيطة تمنع تنفيذ الطلب ما لم يرسل Client مفتاحًا سريًا صحيحًا.
إحدى الطرق المستخدمة في هذا السيناريو هي:
API Key Authenticationحيث يقوم Client بإرسال مفتاح سري مع كل Request، ويقوم Laravel بالتحقق منه قبل السماح للطلب بالوصول إلى Controller.
في هذا الدرس سنتعرف على كيفية إنشاء Middleware مخصص للتحقق من API Key، وكيفية إرسال المفتاح داخل HTTP Header، وتسجيل Middleware بالطريقة الحديثة في Laravel، بالإضافة إلى توضيح الفرق بين API Key وبين Laravel Sanctum.
جدول المحتويات
- ما المشكلة التي نحاول حلها؟
- ما هو API Key؟
- هل API Key هو كلمة مرور للمستخدم؟
- متى نستخدم API Key؟
- متى لا نستخدم API Key؟
- لماذا لا نرسل المفتاح داخل URL؟
- إرسال API Key داخل Header
- إنشاء API Key قوي
- تخزين المفتاح في Environment
- إضافة المفتاح إلى Configuration
- إنشاء Middleware
- كتابة منطق التحقق
- لماذا نستخدم hash_equals؟
- تسجيل Middleware في Laravel الحديث
- حماية Route واحدة
- حماية مجموعة Routes
- استخدام API Key مع Versioning
- اختبار API Key باستخدام Postman
- استجابة Unauthorized
- استخدام Authorization Bearer بدل X-API-Key
- ماذا لو كان لدينا أكثر من Client؟
- API Key Rotation
- لماذا لا نخزن API Key في Frontend أو تطبيق الهاتف؟
- API Key أم Laravel Sanctum؟
- دمج API Key مع Rate Limiting
- ضرورة استخدام HTTPS
- أفضل الممارسات الأمنية
- مثال عملي متكامل
- ملخص الدرس
- الخلاصة
ما المشكلة التي نحاول حلها؟
لنفترض أن لدينا Endpoint:
GET /api/v1/brandsإذا لم يكن Route محميًا بأي نوع من Authentication، فإن أي Client يستطيع إرسال Request إليه والوصول إلى البيانات التي يسمح بها Endpoint.
لكن ماذا لو كان هذا API مخصصًا فقط لخادم آخر نملكه؟
مثل:
CRM Server
|
| API Request
v
Laravel Applicationهنا يمكن أن نطلب من CRM Server إرسال Secret Key مع كل Request.
إذا كان المفتاح صحيحًا:
Request
|
v
API Key Middleware
|
| Valid
v
Controllerأما إذا كان المفتاح غير صحيح:
Request
|
v
API Key Middleware
|
| Invalid
v
401 Unauthorizedما هو API Key؟
API Key هو قيمة سرية يستخدمها Client للتعريف بنفسه أو لإثبات أنه مسموح له باستخدام API معين.
مثلًا:
X-API-Key: random-secret-keyLaravel يستقبل Request، ثم يقوم Middleware بمقارنة المفتاح المرسل بالمفتاح الذي نثق به.
إذا تطابق المفتاح يسمح للطلب بالمرور.
هل API Key هو كلمة مرور للمستخدم؟
لا.
يجب التفريق بين:
User Authenticationو:
Application / Service Authenticationاسم المستخدم وكلمة المرور عادةً يمثلان مستخدمًا حقيقيًا.
أما API Key البسيط في هذا الدرس فيمثل Client أو Service مسموحًا له بالاتصال بالـAPI.
مثلًا:
User
|
| Email + Password
v
Authentication
CRM Server
|
| API Key
v
APIمتى نستخدم API Key؟
يمكن أن يكون API Key مناسبًا في سيناريوهات مثل:
- Server-to-Server Integration.
- Webhook Integration عندما يكون التصميم مبنيًا على Secret مناسب.
- API داخلي بين نظامين موثوقين.
- خدمات داخلية لا تحتاج إلى حساب مستخدم مستقل.
- التعريف بالـClient الذي يستهلك API.
على سبيل المثال:
Website Backend
|
| X-API-Key
v
Internal Laravel APIمتى لا يكون API Key الثابت مناسبًا؟
API Key الثابت ليس حلًا مثاليًا لجميع أنواع Authentication.
إذا كان لدينا مستخدمون يسجلون الدخول إلى تطبيق Android أو iOS أو SPA، فالأفضل استخدام نظام Authentication مصمم للمستخدمين مثل:
Laravel Sanctumخصوصًا عندما نحتاج إلى:
- معرفة المستخدم الحالي.
- إصدار Tokens مختلفة لكل مستخدم.
- إلغاء Token معين.
- Token Abilities.
- تسجيل الخروج.
- إدارة Sessions.
لماذا لا نرسل API Key داخل URL؟
قد نرى أحيانًا طريقة مثل:
GET /api/v1/brands?api_password=my-secret-keyهذه الطريقة غير مفضلة أمنيًا.
Query String قد يتم تسجيلها في عدة أماكن، مثل:
- Web Server Logs.
- Reverse Proxy Logs.
- Browser History.
- Monitoring Systems.
- Analytics Tools.
- Debugging Logs.
لذلك لا ينبغي وضع Secrets الحساسة داخل URL كلما كان بالإمكان تجنب ذلك.
الأفضل إرسالها في Header.
إرسال API Key داخل Header
سنستخدم Header باسم:
X-API-Keyفيصبح Request مثل:
GET /api/v1/brands
X-API-Key: your-secret-api-keyبهذا يبقى المفتاح منفصلًا عن URL.
إنشاء API Key قوي
لا تستخدم قيمة سهلة مثل:
123456أو:
passwordأو:
my_api_passwordAPI Key يجب أن يحتوي على Entropy كافية وأن يكون من الصعب تخمينه.
يمكن مثلًا إنشاء قيمة عشوائية داخل Laravel باستخدام:
php artisan tinkerثم:
use Illuminate\Support\Str;
Str::random(64);سنفترض أن الناتج كان:
YOUR_RANDOM_64_CHARACTER_API_KEYالقيمة هنا مجرد مثال، ويجب توليد Secret حقيقي خاص بالتطبيق وعدم نشره في المقالات أو Git Repository.
تخزين API Key داخل .env
يمكن وضع المفتاح داخل:
.envمثل:
INTERNAL_API_KEY=YOUR_RANDOM_64_CHARACTER_API_KEYيجب عدم رفع ملف:
.envإلى Git Repository.
كما يجب عدم كتابة المفتاح الحقيقي مباشرة داخل Middleware.
إضافة API Key إلى Configuration
بدل استدعاء:
env()مباشرة داخل Middleware، من الأفضل وضع القيمة داخل Configuration ثم استخدام:
config()مثلًا يمكن إنشاء ملف:
config/api.phpووضع:
<?php
return [
'key' => env('INTERNAL_API_KEY'),
];بعد ذلك يمكن قراءة القيمة داخل التطبيق باستخدام:
config('api.key')وهذا يجعل إعدادات التطبيق أكثر تنظيمًا ويتوافق بصورة أفضل مع Configuration Caching في بيئة Production.
إنشاء Middleware للتحقق من API Key
سننشئ Middleware باسم:
EnsureApiKeyIsValidباستخدام:
php artisan make:middleware EnsureApiKeyIsValidسيتم إنشاء الملف داخل:
app/Http/Middleware/EnsureApiKeyIsValid.phpبهيكل قريب من:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureApiKeyIsValid
{
public function handle(
Request $request,
Closure $next
): Response {
return $next($request);
}
}كتابة منطق التحقق من API Key
نريد الحصول على المفتاح من:
X-API-Keyثم مقارنته بالمفتاح الموجود داخل Configuration.
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureApiKeyIsValid
{
public function handle(
Request $request,
Closure $next
): Response {
$providedKey = $request->header('X-API-Key');
$validKey = config('api.key');
if (
! is_string($providedKey) ||
! is_string($validKey) ||
! hash_equals($validKey, $providedKey)
) {
return response()->json([
'message' => 'Unauthorized.',
], 401);
}
return $next($request);
}
}أصبح Middleware الآن يقوم بثلاث خطوات:
- قراءة API Key من Header.
- قراءة المفتاح الصحيح من Configuration.
- السماح للـRequest أو رفضه.
لماذا نستخدم hash_equals؟
قد يبدو من الممكن استخدام:
$providedKey === $validKeyلكن عند مقارنة Secrets من الأفضل استخدام:
hash_equals()وهي دالة PHP مصممة لمقارنة Strings بطريقة تقلل تسريب معلومات عن المقارنة عبر Timing Differences.
لذلك استخدمنا:
hash_equals(
$validKey,
$providedKey
)تسجيل Middleware في Laravel الحديث
في الإصدارات الحديثة من Laravel يتم تسجيل Middleware aliases في:
bootstrap/app.phpوليس في:
app/Http/Kernel.phpنضيف:
use App\Http\Middleware\EnsureApiKeyIsValid;
use Illuminate\Foundation\Configuration\Middleware;ثم داخل:
->withMiddleware()نكتب:
->withMiddleware(
function (Middleware $middleware): void {
$middleware->alias([
'api.key' => EnsureApiKeyIsValid::class,
]);
}
)أصبح لدينا Middleware Alias باسم:
api.keyحماية Route واحدة
يمكن الآن كتابة:
Route::get(
'/brands',
[BrandController::class, 'index']
)->middleware('api.key');أي Request إلى Endpoint يجب أن يجتاز Middleware أولًا.
حماية مجموعة Routes
في الغالب نريد حماية مجموعة كاملة من Endpoints.
يمكن استخدام:
Route::middleware('api.key')
->group(function () {
Route::apiResource(
'brands',
BrandController::class
);
});وبذلك يتم تطبيق API Key Authentication على جميع Routes الخاصة بـBrands.
استخدام API Key مع API Versioning
في درس Versioning أنشأنا Routes مثل:
/api/v1/brands
/api/v2/brandsيمكن دمج Versioning مع API Key بسهولة:
Route::prefix('v1')
->middleware('api.key')
->group(function () {
Route::apiResource(
'brands',
V1BrandController::class
);
});الآن:
GET /api/v1/brandsلن يعمل إلا إذا تم إرسال API Key الصحيح.
اختبار API Key باستخدام Postman
إذا أرسلنا:
GET /api/v1/brandsبدون API Key، نحصل على:
401 Unauthorizedلاختبار الطلب من Postman نذهب إلى:
Headersثم نضيف:
Key:
X-API-Key
Value:
YOUR_RANDOM_64_CHARACTER_API_KEYثم نرسل Request.
إذا كان المفتاح صحيحًا يصل Request إلى Controller.
استجابة Unauthorized
إذا لم يتم إرسال المفتاح أو كان غير صحيح، يعيد Middleware:
{
"message": "Unauthorized."
}مع:
401 Unauthorizedولا يتم تنفيذ Controller.
لاحظ أن Response لا تخبر Client:
- ما هو المفتاح الصحيح.
- أي جزء من المفتاح خاطئ.
- هل المفتاح الموجود قريب من القيمة الصحيحة.
وهذا سلوك أفضل من إعطاء معلومات غير ضرورية للمستخدم غير المصرح له.
استخدام Authorization Bearer بدل X-API-Key
هناك تصميم آخر يمكن استخدامه وهو إرسال Secret داخل:
Authorizationبالشكل:
Authorization: Bearer YOUR_API_KEYLaravel يستطيع قراءة Bearer Token من Request.
مثلًا:
$providedKey = $request->bearerToken();فيصبح Middleware:
public function handle(
Request $request,
Closure $next
): Response {
$providedKey = $request->bearerToken();
$validKey = config('api.key');
if (
! is_string($providedKey) ||
! is_string($validKey) ||
! hash_equals($validKey, $providedKey)
) {
return response()->json([
'message' => 'Unauthorized.',
], 401);
}
return $next($request);
}لكن يجب الانتباه إلى أن استخدام كلمة:
Bearerلا يحول المفتاح تلقائيًا إلى نظام Tokens متكامل.
في هذا المثال ما زلنا نقارن Secret ثابتًا، وليس Laravel Sanctum Personal Access Token.
ماذا لو كان لدينا أكثر من Client؟
استخدام API Key واحدة مشتركة بين جميع الأنظمة بسيط لكنه محدود.
لنفترض أن لدينا:
CRM
Mobile Backend
Reporting Server
Partner Integrationإذا استخدمت جميعها المفتاح نفسه، ثم تسرب المفتاح، سنحتاج إلى تغييره لجميع الأنظمة.
كما أننا لن نعرف بسهولة أي Client قام بإرسال Request.
في الأنظمة الأكثر تقدمًا يفضل أن يكون لكل Client Credential مستقل.
مثلًا:
Client A
|
+--- API Key A
Client B
|
+--- API Key B
Client C
|
+--- API Key Cعندها يمكن:
- إلغاء Client واحد فقط.
- تطبيق Rate Limit مختلف لكل Client.
- تسجيل استخدام كل Client.
- إعطاء صلاحيات مختلفة.
- تدوير المفتاح بشكل مستقل.
ما هو API Key Rotation؟
API Key لا ينبغي اعتباره Secret سيبقى إلى الأبد.
من الأفضل وجود آلية لتغييره عند الحاجة، خصوصًا إذا:
- تم الاشتباه بتسريبه.
- غادر أحد الأشخاص الذين لديهم صلاحية الوصول.
- ظهر المفتاح في Log أو Repository بالخطأ.
- تحتاج سياسة المؤسسة إلى تغيير Secrets دوريًا.
وتسمى عملية استبدال Secret:
Key Rotationفي الأنظمة الكبيرة قد ندعم مفتاحين مؤقتًا:
Current Key
New Keyثم يتم تحديث Clients وإلغاء المفتاح القديم.
لماذا لا نخزن Secret API Key داخل Frontend أو تطبيق الهاتف؟
هذه نقطة أمنية مهمة جدًا.
إذا وضعت API Key ثابتًا داخل:
- JavaScript Frontend.
- React Application.
- Vue Application.
- Flutter Application.
- Android APK.
- iOS Application.
فيجب ألا تفترض أن المفتاح سيبقى سريًا.
الكود الذي يصل إلى جهاز المستخدم يمكن تحليله أو مراقبة Requests الخاصة به، وبالتالي يمكن استخراج Credentials الثابتة في كثير من الحالات.
لذلك API Key ثابت مناسب أكثر لسيناريو:
Server
|
| Secret
v
Serverوليس كبديل عن User Authentication داخل تطبيق يتم توزيعه للمستخدمين.
API Key أم Laravel Sanctum؟
يعتمد الاختيار على ما تريد حمايته.
| الحالة | الحل المناسب غالبًا |
|---|---|
| Server يتصل بـServer داخلي | API Key أو آلية Service Authentication مناسبة |
| مستخدم يسجل الدخول من تطبيق الهاتف | Laravel Sanctum |
| SPA تابع لنفس التطبيق | Sanctum SPA Authentication |
| Personal Access Tokens | Laravel Sanctum |
| صلاحيات Token لكل مستخدم | Sanctum Token Abilities |
| OAuth2 كامل | حل OAuth مناسب مثل Laravel Passport عند الحاجة |
API Key البسيط لا يوفر تلقائيًا:
- User Authentication.
- Token Revocation لكل مستخدم.
- Token Abilities.
- Sessions.
- OAuth Flows.
لذلك يجب اختيار التقنية بناءً على نوع Client وليس فقط على سهولة التطبيق.
دمج API Key مع Rate Limiting
API Key يجيب عن سؤال:
هل هذا Client مسموح له بالوصول؟
أما Rate Limiting فيجيب عن:
كم عدد Requests التي يستطيع إرسالها؟
لذلك يمكن استخدام الطبقتين معًا:
Route::middleware([
'api.key',
'throttle:api',
])->group(function () {
Route::apiResource(
'brands',
BrandController::class
);
});ويصبح التدفق:
Request
|
v
API Key Check
|
v
Rate Limiting
|
v
Controllerضرورة استخدام HTTPS
نقل API Key داخل Header لا يعني أن الاتصال أصبح آمنًا إذا تم إرسال الطلب عبر HTTP غير مشفر.
يجب استخدام:
HTTPSحتى يتم تشفير الاتصال أثناء انتقال البيانات بين Client وServer.
ولا ينبغي إرسال Secrets عبر:
http://في بيئة Production.
أفضل الممارسات الأمنية
لا ترسل API Key داخل Query String
تجنب:
?api_key=secretواستخدم Header.
لا تضع مفتاحًا افتراضيًا داخل Source Code
تجنب:
env(
'API_KEY',
'default-password'
)لأن وجود Default Secret معروف يعني أن خطأ Configuration قد يؤدي إلى استخدام مفتاح يمكن توقعه.
إذا لم يكن Secret مضبوطًا، ارفض الطلب
Configuration المفقودة يجب ألا تؤدي إلى تشغيل النظام باستخدام Password افتراضية.
لا ترفع Secrets إلى Git
لا تضع:
API_KEY=real-secretفي Repository.
استخدم HTTPS دائمًا
خصوصًا عند إرسال Authentication Credentials.
استخدم Keys مختلفة للعملاء المختلفين عند الحاجة
هذا يسهل Revocation وAuditing وRate Limiting.
غير المفتاح إذا تم تسريبه
لا تعتمد على Secret معروف أنه تعرض للكشف.
أضف Rate Limiting
وجود Secret صحيح لا يعني السماح بعدد غير محدود من Requests.
سجل الاستخدام بدون تسجيل Secret نفسه
يمكن تسجيل:
- Client ID.
- Endpoint.
- Timestamp.
- Status Code.
لكن تجنب وضع API Key نفسه داخل Application Logs.
لا تستخدم Static API Key كبديل عن User Authentication
إذا كانت العملية مرتبطة بمستخدم حقيقي، فاستخدم Authentication مناسبًا مثل Sanctum حسب السيناريو.
مثال عملي متكامل
1. إنشاء Secret
داخل:
.envنضيف:
INTERNAL_API_KEY=YOUR_RANDOM_64_CHARACTER_API_KEY2. إنشاء Configuration
الملف:
config/api.phpيحتوي على:
<?php
return [
'key' => env('INTERNAL_API_KEY'),
];3. إنشاء Middleware
php artisan make:middleware EnsureApiKeyIsValid4. كتابة Middleware
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class EnsureApiKeyIsValid
{
public function handle(
Request $request,
Closure $next
): Response {
$providedKey = $request->header(
'X-API-Key'
);
$validKey = config('api.key');
if (
! is_string($providedKey) ||
! is_string($validKey) ||
! hash_equals(
$validKey,
$providedKey
)
) {
return response()->json([
'message' => 'Unauthorized.',
], 401);
}
return $next($request);
}
}5. تسجيل Middleware Alias
داخل:
bootstrap/app.phpنضيف:
use App\Http\Middleware\EnsureApiKeyIsValid;
use Illuminate\Foundation\Configuration\Middleware;ثم:
->withMiddleware(
function (Middleware $middleware): void {
$middleware->alias([
'api.key' => EnsureApiKeyIsValid::class,
]);
}
)6. حماية API Routes
داخل:
routes/api.phpنكتب:
Route::prefix('v1')
->middleware([
'api.key',
'throttle:api',
])
->group(function () {
Route::apiResource(
'brands',
BrandController::class
);
});7. إرسال Request
GET /api/v1/brands
Accept: application/json
X-API-Key: YOUR_RANDOM_64_CHARACTER_API_KEY8. API Key غير صحيح
Response:
HTTP 401 Unauthorized{
"message": "Unauthorized."
}9. API Key صحيح
يمر Request عبر Middleware ويتم تنفيذ:
BrandControllerثم يعيد API البيانات المطلوبة.
ملخص الدرس
| المفهوم | الوصف |
|---|---|
| API Key | Secret يستخدم للسماح لـClient أو Service بالوصول إلى API. |
| X-API-Key | HTTP Header يمكن استخدامه لإرسال API Key. |
| Middleware | طبقة تفحص Request قبل وصوله إلى Controller. |
| hash_equals() | طريقة مناسبة لمقارنة Strings حساسة مثل Secrets. |
| 401 Unauthorized | استجابة عند عدم تقديم Credentials صحيحة. |
| config() | قراءة إعدادات التطبيق من Configuration. |
| .env | مكان Environment Variables الخاصة بالبيئة. |
| bootstrap/app.php | المكان الحديث لتسجيل Middleware aliases في Laravel. |
| Sanctum | حل Authentication مناسب للمستخدمين وAPI Tokens. |
| Rate Limiting | تحديد عدد Requests المسموح بها. |
| HTTPS | تشفير الاتصال بين Client وServer. |
الخلاصة
تعرفنا في هذا الجزء على طريقة إضافة طبقة حماية بسيطة إلى REST API باستخدام API Key وMiddleware مخصص.
بدل إرسال كلمة المرور داخل URL:
?api_password=secretاستخدمنا Header:
X-API-Key: secretثم أنشأنا Middleware يقوم بقراءة المفتاح ومقارنته بالمفتاح الموجود داخل Configuration قبل السماح للـRequest بالوصول إلى Controller.
كما سجلنا Middleware Alias بالطريقة الحديثة داخل:
bootstrap/app.phpواستخدمناه مع Routes:
middleware('api.key')وتعرفنا أيضًا على نقطة مهمة: API Key الثابت ليس بديلًا عن نظام Authentication للمستخدمين.
إذا كان لدينا تطبيق هاتف أو SPA يحتوي على حسابات مستخدمين، فإن حلولًا مثل Laravel Sanctum تكون أكثر ملاءمة لأنها توفر Tokens مرتبطة بالمستخدمين ويمكن إدارتها وإلغاؤها وتحديد صلاحياتها.
أما API Key البسيط فيكون مفيدًا أكثر عندما نحتاج إلى حماية الاتصال بين خدمات أو Servers موثوقة.
وأخيرًا، API Key يجب ألا يكون طبقة الحماية الوحيدة. يمكن دمجه مع HTTPS وRate Limiting وAuthentication وAuthorization وLogging حسب حساسية النظام وطبيعة API.
التعليقات (0)
لا توجد تعليقات بعد — كن أول من يشارك رأيه.
أضف تعليقك