كلاود
أدلة

كيف تجهّز سيرفرك لربط النفاذ الوطني

كلاود|٢٦ فبراير ٢٠٢٦|5 دقائق قراءة|٦٧٠ مشاهدة
كيف تجهّز سيرفرك لربط النفاذ الوطني

سيرفر سعودي + SSL + IP ثابت + إعدادات أمان محددة. الخطوات التقنية من تقديم الطلب للتشغيل.

مقدمة في التكامل مع النفاذ الوطني

النفاذ الوطني (Nafath) هو الطريق الوحيد للتحقق من هوية المستخدمين السعوديين والمقيمين رقمياً. التكامل معه يفتح إمكانية خدمة آلاف العملاء دون الحاجة لزيارات حضورية أو وثائق ورقية.

هذا الدليل يشرح التكامل التقني خطوة بخطوة — من إعداد السيرفر حتى كتابة الكود.

المتطلبات الأساسية قبل البدء

1. سيرفر في السعودية مع IP ثابت مخصص
 (راجع مقال متطلبات السيرفر للنفاذ الوطني)

2. اعتماد رسمي من هيئة الاتصالات
 - Client ID
 - Client Secret

3. اشتراك نشط في ZATCA (لبعض أنواع التكامل)

4. HTTPS بشهادة صالحة من CA معتمد

آلية عمل النفاذ الوطني

تدفق التحقق:
┌─────────────────────────────────────────────┐
│ 1. المستخدم يطلب الدخول/التحقق │
│ ↓ │
│ 2. تطبيقك يرسل طلب لـ Nafath API │
│ {nationalId, random, service} │
│ ↓ │
│ 3. Nafath يرسل إشعاراً لجوال المستخدم │
│ (تطبيق النفاذ) │
│ ↓ │
│ 4. المستخدم يوافق على التطبيق │
│ ↓ │
│ 5. تطبيقك يستطلع نتيجة الموافقة │
│ ↓ │
│ 6. عند الموافقة: متابعة المصادقة │
└─────────────────────────────────────────────┘

التكامل العملي مع Laravel

تثبيت وإعداد

# لا توجد مكتبة رسمية بعد - نستخدم HTTP Client
composer require guzzlehttp/guzzle
// config/nafath.php
return [
 'base_url' => env('NAFATH_BASE_URL', 'https://nafath.api.iam.gov.sa/v1'),
 'app_id' => env('NAFATH_APP_ID'),
 'app_key' => env('NAFATH_APP_KEY'),
 'service_code' => env('NAFATH_SERVICE_CODE'),
 'timeout' => 30,
 'retry_times' => 3,
 'retry_sleep' => 2000, // ms
];
# .env
NAFATH_BASE_URL=https://nafath.api.iam.gov.sa/v1
NAFATH_APP_ID=your_app_id
NAFATH_APP_KEY=your_app_key
NAFATH_SERVICE_CODE=your_service_code

خدمة التكامل

// app/Services/NafathService.php
class NafathService
{
 private string $baseUrl;
 private string $appId;
 private string $appKey;

 public function __construct
 {
 $this->baseUrl = config('nafath.base_url');
 $this->appId = config('nafath.app_id');
 $this->appKey = config('nafath.app_key');
 }

 /**
 * بدء طلب تحقق جديد
 */
 public function initiateVerification(string $nationalId, string $random): array
 {
 $response = Http::timeout(config('nafath.timeout'))
 ->withHeaders([
 'APP-ID' => $this->appId,
 'APP-KEY' => $this->appKey,
 'Content-Type' => 'application/json',
 ])
 ->post("{$this->baseUrl}/moi/checkStatus", [
 'nationalId' => $nationalId,
 'random' => $random,
 'serviceCode' => config('nafath.service_code'),
 ]);

 if ($response->failed) {
 throw new NafathException(
 "Nafath API error: {$response->status} — {$response->body}"
 );
 }

 return $response->json;
 }

 /**
 * التحقق من حالة الطلب
 */
 public function checkStatus(string $nationalId, string $random, string $transId): array
 {
 $response = Http::timeout(config('nafath.timeout'))
 ->withHeaders([
 'APP-ID' => $this->appId,
 'APP-KEY' => $this->appKey,
 ])
 ->get("{$this->baseUrl}/moi/checkStatus", [
 'nationalId' => $nationalId,
 'random' => $random,
 'transId' => $transId,
 ]);

 return $response->json;
 }

 /**
 * توليد random string آمن
 */
 public function generateRandom: string
 {
 return strtoupper(substr(bin2hex(random_bytes(4)), 0, 8));
 }
}

Controller

// app/Http/Controllers/NafathController.php
class NafathController extends Controller
{
 public function __construct(private NafathService $nafath) {}

 /**
 * بدء عملية التحقق
 */
 public function initiate(Request $request): JsonResponse
 {
 $request->validate([
 'national_id' => ['required', 'digits:10'],
 ]);

 $random = $this->nafath->generateRandom;

 $result = $this->nafath->initiateVerification(
 $request->national_id,
 $random
 );

 // حفظ البيانات في الجلسة للتحقق لاحقاً
 session([
 'nafath_national_id' => $request->national_id,
 'nafath_random' => $random,
 'nafath_trans_id' => $result['transId'],
 'nafath_expires_at' => now->addMinutes(5),
 ]);

 return response->json([
 'random' => $random,
 'trans_id' => $result['transId'],
 'status' => $result['status'],
 ]);
 }

 /**
 * فحص حالة الموافقة (يُستدعى كل 5 ثوانٍ)
 */
 public function status(Request $request): JsonResponse
 {
 if (!session('nafath_trans_id')) {
 return response->json(['error' => 'No active verification'], 400);
 }

 if (now->isAfter(session('nafath_expires_at'))) {
 session->forget(['nafath_national_id', 'nafath_random', 'nafath_trans_id']);
 return response->json(['status' => 'EXPIRED']);
 }

 $result = $this->nafath->checkStatus(
 session('nafath_national_id'),
 session('nafath_random'),
 session('nafath_trans_id')
 );

 if ($result['status'] === 'COMPLETED') {
 // تم التحقق — إنشاء مستخدم أو تحديث البيانات
 $this->handleVerificationComplete($result);
 session->forget(['nafath_national_id', 'nafath_random', 'nafath_trans_id']);
 }

 return response->json([
 'status' => $result['status'],
 'message' => $this->statusMessage($result['status']),
 ]);
 }

 private function statusMessage(string $status): string
 {
 return match($status) {
 'WAITING' => 'في انتظار موافقتك على تطبيق النفاذ',
 'COMPLETED' => 'تم التحقق بنجاح',
 'REJECTED' => 'تم رفض الطلب',
 'EXPIRED' => 'انتهت صلاحية الطلب',
 default => 'حالة غير معروفة',
 };
 }
}

Frontend (React + Polling)

// NafathVerification.tsx
export function NafathVerification {
 const [status, setStatus] = useState<'idle' | 'waiting' | 'success' | 'failed'>('idle');
 const [random, setRandom] = useState('');

 const initiateVerification = async (nationalId: string) => {
 const res = await fetch('/nafath/initiate', {
 method: 'POST',
 headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
 body: JSON.stringify({ national_id: nationalId }),
 });
 const data = await res.json;
 setRandom(data.random);
 setStatus('waiting');
 startPolling;
 };

 const startPolling = => {
 const interval = setInterval(async => {
 const res = await fetch('/nafath/status');
 const data = await res.json;

 if (data.status === 'COMPLETED') {
 clearInterval(interval);
 setStatus('success');
 } else if (['REJECTED', 'EXPIRED'].includes(data.status)) {
 clearInterval(interval);
 setStatus('failed');
 }
 }, 5000); // كل 5 ثوانٍ
 };

 if (status === 'waiting') {
 return (
 <div>
 <p>افتح تطبيق النفاذ الوطني وأدخل الرمز:</p>
 <strong className="text-4xl tracking-widest">{random}</strong>
 </div>
 );
 }

 // ... عرض باقي الحالات
}

الأمان والامتثال

// Rate Limiting لمنع إساءة الاستخدام
Route::middleware(['throttle:5,1'])->group(function {
 Route::post('/nafath/initiate', [NafathController::class, 'initiate']);
});

// تسجيل جميع محاولات التحقق
Log::channel('nafath')->info('Verification attempt', [
 'national_id_hash' => hash('sha256', $nationalId), // لا تسجل الهوية كاملة
 'ip' => request->ip,
 'timestamp' => now->toISOString,
]);

اختبار التكامل

# اختبار في Sandbox
curl -X POST https://nafath.api.iam.gov.sa/sandbox/moi/checkStatus \
 -H "APP-ID: test_app_id" \
 -H "APP-KEY: test_app_key" \
 -H "Content-Type: application/json" \
 -d '{
 "nationalId": "1234567890",
 "random": "ABCD1234",
 "serviceCode": "TST"
 }'

على سيرفرات كلاود

سيرفرات كلاود (cloud.net.sa) في الرياض وجدة تستوفي جميع متطلبات النفاذ الوطني. مع توفر Dedicated IP وSLA 99.99%، يمكن لتطبيقك الحصول على اعتماد الإنتاج بدون عقبات تقنية.

خلاصة

التكامل مع النفاذ الوطني ليس معقداً تقنياً — هو straightforward HTTP API. التحدي الحقيقي هو الامتثال: السيرفر السعودي، IP ثابت، HTTPS، وسجلات التدقيق. أحكم هذه الأساسيات وسيعمل التكامل بسلاسة.


ابدأ الآن

تبي تجرب بنفسك؟ اختر باقة الاستضافة المناسبة أو صمم سيرفرك الخاص بالمواصفات اللي تبيها.

مشاركة