تعلم كيفية بناء أداة التحقق من بيانات الاعتماد الرقمية من الصفر باستخدام Next.js، وOpenID4VP، وISO mDoc. يوضح هذا الدليل المفصل للمطورين كيفية إنشاء أداة تحقق يمكنها طلب واستلام والتحقق من صحة رخص القيادة المحمولة وبيانات الاعتماد الرقمية الأخرى.
Amine
Created: August 20, 2025
Updated: August 21, 2025
See the original blog version in English here.
يُعد إثبات الهويات عبر الإنترنت تحديًا مستمرًا، مما أدى إلى الاعتماد على كلمات المرور ومشاركة المستندات الحساسة عبر قنوات غير آمنة. وقد جعل هذا عملية التحقق من الهوية بالنسبة للشركات بطيئة ومكلفة وعرضة للاحتيال. تقدم بيانات الاعتماد الرقمية نهجًا جديدًا يعيد للمستخدمين السيطرة على بياناتهم. إنها المعادل الرقمي لـ محفظة مادية، تحتوي على كل شيء بدءًا من رخصة القيادة إلى شهادة جامعية، ولكن مع مزايا إضافية تتمثل في كونها آمنة تشفيريًا، وتحافظ على الخصوصية، ويمكن التحقق منها فورًا.
يقدم هذا الدليل للمطورين برنامجًا تعليميًا عمليًا خطوة بخطوة لبناء أداة تحقق من بيانات الاعتماد الرقمية. على الرغم من وجود المعايير، إلا أن هناك القليل من الإرشادات حول كيفية تنفيذها. يملأ هذا البرنامج التعليمي هذه الفجوة، حيث يوضح لك كيفية بناء أداة تحقق باستخدام واجهة برمجة تطبيقات بيانات الاعتماد الرقمية (Digital Credential API) المدمجة في المتصفح، وبروتوكول OpenID4VP لبروتوكول العرض، ومعيار ISO mDoc (مثل رخصة القيادة المحمولة) كتنسيق لبيانات الاعتماد.
ستكون النتيجة النهائية تطبيق Next.js بسيطًا ولكنه عملي يمكنه طلب واستلام والتحقق من صحة بيانات اعتماد رقمية من محفظة محمولة متوافقة.
إليكم نظرة سريعة على التطبيق النهائي أثناء عمله. تتضمن العملية أربع خطوات رئيسية:
الخطوة 1: الصفحة الأولية يصل المستخدم إلى الصفحة الأولية وينقر على "التحقق باستخدام الهوية الرقمية" لبدء العملية.
الخطوة 2: طلب الثقة يطالب المتصفح المستخدم بالثقة. ينقر المستخدم على "متابعة" للمضي قدمًا.
الخطوة 3: مسح رمز الاستجابة السريعة (QR) يتم عرض رمز QR، الذي يمسحه المستخدم باستخدام تطبيق المحفظة المتوافق.
الخطوة 4: بيانات الاعتماد المفككة بعد التحقق الناجح، يعرض التطبيق بيانات الاعتماد المفككة.
Recent Articles
📝
كيفية بناء أداة التحقق من بيانات الاعتماد الرقمية (دليل المطورين)
📝
كيفية بناء جهة إصدار بيانات اعتماد رقمية (دليل المطورين)
📖
المفتاح المقيم في WebAuthn: بيانات الاعتماد القابلة للاكتشاف كـ Passkeys
🔑
الوصول بالشارات المادية ومفاتيح المرور: دليل تقني
🔑
إلزامية المصادقة متعددة العوامل (MFA) والتوجه نحو مفاتيح المرور (Passkeys): أفضل الممارسات
يكمن السحر وراء بيانات الاعتماد الرقمية في نموذج "مثلث الثقة" البسيط والفعال الذي يضم ثلاثة لاعبين رئيسيين:
عندما يرغب المستخدم في الوصول إلى خدمة ما، فإنه يقدم بيانات الاعتماد من محفظته. يمكن للمُحقِّق بعد ذلك التحقق من صحتها على الفور دون الحاجة إلى الاتصال بـ جهة الإصدار الأصلية مباشرة.
لكي يزدهر نظام الهوية اللامركزي هذا، فإن دور المُحقِّق حاسم للغاية. فهم حراس هذه البنية التحتية الجديدة للثقة، وهم الذين يستهلكون بيانات الاعتماد ويجعلونها مفيدة في العالم الحقيقي. كما يوضح الرسم البياني أدناه، يكمل المُحقِّق مثلث الثقة عن طريق طلب واستلام والتحقق من صحة بيانات اعتماد من الحامل.
إذا كنت مطورًا، فإن بناء خدمة لتنفيذ هذا التحقق هو مهارة أساسية للجيل القادم من التطبيقات الآمنة والمتمحورة حول المستخدم. تم تصميم هذا الدليل لإرشادك خلال هذه العملية بالضبط. سنغطي كل ما تحتاج إلى معرفته لـ بناء أداة التحقق الخاصة بك من بيانات الاعتماد القابلة للتحقق، من المفاهيم والمعايير الأساسية إلى تفاصيل التنفيذ خطوة بخطوة للتحقق من التوقيعات والتحقق من حالة بيانات الاعتماد.
هل تريد التخطي إلى الأمام؟ يمكنك العثور على المشروع الكامل والنهائي لهذا البرنامج التعليمي على GitHub. لا تتردد في نسخه وتجربته بنفسك: https://github.com/corbado/digital-credentials-example
لنبدأ.
قبل أن تبدأ، تأكد من أن لديك:
سنستعرض الآن كل من هذه المتطلبات بالتفصيل، بدءًا من المعايير والبروتوكولات التي يقوم عليها هذا المُحقِّق القائم على mdoc.
تم بناء أداة التحقق الخاصة بنا من أجل ما يلي:
المعيار / البروتوكول | الوصف |
---|---|
W3C VC | نموذج بيانات W3C Verifiable Credentials. يحدد الهيكل القياسي لبيانات الاعتماد الرقمية، بما في ذلك الادعاءات والبيانات الوصفية والإثباتات. |
SD-JWT | الإفصاح الانتقائي لـ JWTs. تنسيق لـ VCs يعتمد على JSON Web Tokens يسمح للحاملين بالكشف الانتقائي عن ادعاءات محددة فقط من بيانات الاعتماد، مما يعزز الخصوصية. |
ISO mDoc | ISO/IEC 18013-5. المعيار الدولي لرخص القيادة المحمولة (mDLs) ومعرفات الهوية المحمولة الأخرى، ويحدد هياكل البيانات وبروتوكولات الاتصال للاستخدام عبر الإنترنت وغير متصل. |
OpenID4VP | OpenID for Verifiable Presentations. بروتوكول عرض قابل للتشغيل البيني مبني على OAuth 2.0. يحدد كيفية طلب المُحقِّق لبيانات الاعتماد وتقديم محفظة الحامل لها. |
في هذا البرنامج التعليمي، نستخدم على وجه التحديد:
ملاحظة حول النطاق: بينما نقدم W3C VC و SD-JWT لتوفير سياق أوسع، فإن هذا البرنامج التعليمي ينفذ حصريًا بيانات اعتماد ISO mDoc عبر OpenID4VP. بيانات الاعتماد المستندة إلى W3C خارج نطاق هذا المثال.
يحدد معيار ISO/IEC 18013-5 mDoc بنية وترميز المستندات المحمولة مثل رخص القيادة المحمولة (mDLs). بيانات اعتماد mDoc مشفرة بـ CBOR، وموقعة تشفيريًا، ويمكن تقديمها رقميًا للتحقق. ستركز أداة التحقق الخاصة بنا على فك تشفير والتحقق من صحة بيانات اعتماد mdoc هذه.
OpenID4VP هو بروتوكول قابل للتشغيل البيني لطلب وتقديم بيانات الاعتماد الرقمية، مبني على OAuth 2.0 و OpenID Connect. في هذا التنفيذ، يتم استخدام OpenID4VP من أجل:
الآن بعد أن أصبح لدينا فهم واضح للمعايير والبروتوكولات، نحتاج إلى اختيار حزمة التقنيات المناسبة لبناء أداة التحقق الخاصة بنا. تم تصميم خياراتنا من أجل المتانة، وتجربة المطور، والتوافق مع النظام البيئي الحديث للويب.
سنستخدم TypeScript لكل من كود الواجهة الأمامية والخلفية. كمجموعة شاملة من JavaScript، فإنها تضيف الكتابة الثابتة، مما يساعد على اكتشاف الأخطاء مبكرًا، وتحسين جودة الكود، وجعل التطبيقات المعقدة أسهل في الإدارة. في سياق حساس أمنيًا مثل التحقق من بيانات الاعتماد، تعد سلامة الأنواع ميزة هائلة.
Next.js هو إطار العمل المفضل لدينا لأنه يوفر تجربة سلسة ومتكاملة لبناء تطبيقات متكاملة.
redirect_uri
لاستلام والتحقق من الرد النهائي من CMWallet بشكل آمن.يعتمد تنفيذنا على مجموعة محددة من المكتبات للواجهة الأمامية والخلفية:
ملاحظة حول openid-client
: قد تستخدم أدوات التحقق الأكثر تقدمًا والجاهزة للإنتاج
مكتبة openid-client
للتعامل مع بروتوكول OpenID4VP مباشرة على الواجهة الخلفية، مما يتيح
ميزات مثل redirect_uri
ديناميكي. في تدفق OpenID4VP المدفوع بالخادم مع redirect_uri
،
سيتم استخدام openid-client
لتحليل والتحقق من صحة استجابات vp_token
مباشرة. في هذا
البرنامج التعليمي، نستخدم تدفقًا أبسط بوساطة المتصفح لا يتطلب ذلك، مما يجعل العملية أسهل
في الفهم.
تضمن هذه الحزمة التقنية تنفيذًا قويًا وآمنًا وقابلًا للتطوير لأداة التحقق يركز على واجهة برمجة تطبيقات بيانات الاعتماد الرقمية للمتصفح وتنسيق بيانات اعتماد ISO mDoc.
لاختبار أداة التحقق الخاصة بك، تحتاج إلى محفظة محمولة يمكنها التفاعل مع واجهة برمجة تطبيقات بيانات الاعتماد الرقمية للمتصفح.
سنستخدم CMWallet، وهي محفظة اختبار قوية متوافقة مع OpenID4VP لـ Android.
كيفية تثبيت CMWallet (Android):
ملاحظة: قم بتثبيت ملفات APK فقط من المصادر التي تثق بها. الرابط المقدم من مستودع المشروع الرسمي.
قبل أن نتعمق في التنفيذ، من الضروري فهم مفاهيم التشفير التي تقوم عليها بيانات الاعتماد القابلة للتحقق. هذا ما يجعلها "قابلة للتحقق" وجديرة بالثقة.
في جوهرها، بيانات الاعتماد القابلة للتحقق هي مجموعة من الادعاءات (مثل الاسم، تاريخ الميلاد، إلخ) تم توقيعها رقميًا من قبل جهة إصدار. يوفر التوقيع الرقمي ضمانين حاسمين:
يتم إنشاء التوقيعات الرقمية باستخدام تشفير المفتاح العام/الخاص (يسمى أيضًا التشفير غير المتماثل). إليك كيفية عمله في سياقنا:
ملاحظة حول DIDs: في هذا البرنامج التعليمي، لا نقوم بحل مفاتيح جهة الإصدار عبر DIDs. في بيئة الإنتاج، عادةً ما تكشف جهات الإصدار عن المفاتيح العامة عبر DIDs أو نقاط نهاية موثوقة أخرى، والتي سيستخدمها المُحقِّق للتحقق التشفيري.
غالبًا ما يتم تنسيق بيانات الاعتماد القابلة للتحقق كـ JSON Web Tokens (JWTs). JWT هو
طريقة مدمجة وآمنة للـ URL لتمثيل الادعاءات التي سيتم نقلها بين طرفين. يحتوي JWT الموقع
(المعروف أيضًا باسم JWS) على ثلاثة أجزاء مفصولة بنقاط (.
):
alg
).vc
)، بما في ذلك issuer
، و credentialSubject
، إلخ.// مثال على بنية JWT [Header].[Payload].[Signature]
ملاحظة: بيانات الاعتماد القابلة للتحقق المستندة إلى JWT خارج نطاق هذه المقالة. يركز هذا التنفيذ على بيانات اعتماد ISO mDoc و OpenID4VP، وليس على بيانات اعتماد W3C القابلة للتحقق أو بيانات الاعتماد المستندة إلى JWT.
لا يكفي أن يعرف المُحقِّق أن بيانات الاعتماد صالحة؛ بل يحتاج أيضًا إلى معرفة أن الشخص الذي يقدم بيانات الاعتماد هو الحامل الشرعي. هذا يمنع أي شخص من استخدام بيانات اعتماد مسروقة.
يتم حل هذا باستخدام عرض قابل للتحقق (VP). VP هو غلاف حول واحد أو أكثر من VCs يتم توقيعه من قبل الحامل نفسه.
التدفق هو كما يلي:
يجب على المُحقِّق الخاص بنا بعد ذلك إجراء فحصين منفصلين للتوقيع:
يضمن هذا الفحص المزدوج كلاً من أصالة بيانات الاعتماد وهوية الشخص الذي يقدمها، مما يخلق نموذج ثقة قويًا وآمنًا.
ملاحظة: مفهوم العروض القابلة للتحقق كما هو محدد في نظام W3C VC خارج نطاق هذه
المقالة. يشير مصطلح العرض القابل للتحقق هنا إلى
استجابة OpenID4VP vp_token
، والتي تتصرف بشكل مشابه لـ W3C VP ولكنها تستند إلى دلالات
ISO mDoc بدلاً من نموذج توقيع JSON-LD الخاص بـ W3C. يركز هذا الدليل
على بيانات اعتماد ISO mDoc و OpenID4VP، وليس على عروض W3C القابلة للتحقق أو التحقق من
صحة توقيعها.
تستخدم بنية المُحقِّق الخاصة بنا واجهة برمجة تطبيقات بيانات الاعتماد الرقمية (Digital Credential API) المدمجة في المتصفح كوسيط آمن لربط تطبيق الويب الخاص بنا بـ CMWallet المحمولة للمستخدم. يبسط هذا النهج التدفق عن طريق السماح للمتصفح بالتعامل مع عرض رمز الاستجابة السريعة الأصلي والاتصال بالمحفظة.
navigator.credentials.get()
، واستلام النتيجة، وإعادة توجيهها إلى الواجهة الخلفية
للتحقق منها.openid4vp
، ويقوم بإنشاء رمز QR أصلي. ثم ينتظر حتى تعيد المحفظة استجابة.إليكم مخطط تسلسلي يوضح التدفق الكامل والدقيق:
شرح التدفق:
/api/verify/start
)، والتي تقوم بإنشاء كائن طلب يحتوي على الاستعلام و nonce، ثم
تعيده.navigator.credentials.get()
مع كائن الطلب.openid4vp
ويعرض رمز QR أصليًا. الوعد
.get()
الآن معلق.ملاحظة: يحدث تدفق رمز الاستجابة السريعة هذا على متصفحات سطح المكتب. على متصفحات
الجوال (Android Chrome مع تمكين العلامة التجريبية)، يمكن
للمتصفح الاتصال مباشرة بـ المحافظ المتوافقة على نفس
الجهاز، مما يلغي الحاجة إلى مسح رمز الاستجابة السريعة. لتمكين هذه الميزة على Android
Chrome، انتقل إلى
chrome://flags#web-identity-digital-credentials
واضبط العلامة على "Enabled".
.get()
على الواجهة الأمامية
أخيرًا، مما يوفر حمولة العرض./api/verify/finish
في الواجهة الخلفية لدينا. تقوم الواجهة الخلفية بالتحقق
من صحة nonce وبيانات الاعتماد.الآن بعد أن أصبح لدينا فهم قوي للمعايير والبروتوكولات والتدفق المعماري، يمكننا البدء في بناء أداة التحقق الخاصة بنا.
تابع معنا أو استخدم الكود النهائي
سنستعرض الآن الإعداد وتنفيذ الكود خطوة بخطوة. إذا كنت تفضل الانتقال مباشرة إلى المنتج النهائي، يمكنك نسخ المشروع الكامل من مستودع GitHub الخاص بنا وتشغيله محليًا.
git clone https://github.com/corbado/digital-credentials-example.git
أولاً، سنقوم بتهيئة مشروع Next.js جديد، وتثبيت التبعيات اللازمة، وبدء قاعدة البيانات الخاصة بنا.
افتح الطرفية (terminal)، وانتقل إلى الدليل حيث تريد إنشاء مشروعك، وقم بتشغيل الأمر التالي. نستخدم App Router و TypeScript و Tailwind CSS لهذا المشروع.
npx create-next-app@latest . --ts --eslint --tailwind --app --src-dir --import-alias "@/*" --use-npm
يقوم هذا الأمر بإنشاء تطبيق Next.js جديد في دليلك الحالي.
بعد ذلك، نحتاج إلى تثبيت المكتبات التي ستتعامل مع فك تشفير CBOR، واتصالات قاعدة البيانات، وتوليد UUID.
npm install cbor-web mysql2 uuid @types/uuid
يقوم هذا الأمر بتثبيت:
cbor-web
: لفك تشفير حمولة بيانات اعتماد mdoc.mysql2
: عميل MySQL لقاعدة البيانات الخاصة بنا.uuid
: لتوليد سلاسل تحدي فريدة.@types/uuid
: أنواع TypeScript لمكتبة uuid
.تتطلب الواجهة الخلفية لدينا قاعدة بيانات MySQL لتخزين بيانات جلسة OIDC، مما يضمن أن كل
تدفق تحقق آمن ومستقر. لقد قمنا بتضمين ملف docker-compose.yml
لجعل هذا الأمر سهلاً.
إذا قمت بنسخ المستودع، يمكنك ببساطة تشغيل docker-compose up -d
. إذا كنت تبني من الصفر،
فأنشئ ملفًا باسم docker-compose.yml
بالمحتوى التالي:
services: mysql: image: mysql:8.0 restart: always environment: MYSQL_ROOT_PASSWORD: rootpassword MYSQL_DATABASE: digital_credentials MYSQL_USER: app_user MYSQL_PASSWORD: app_password ports: - "3306:3306" volumes: - mysql_data:/var/lib/mysql - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] timeout: 20s retries: 10 volumes: mysql_data:
يتطلب إعداد Docker Compose هذا أيضًا نصًا برمجيًا لتهيئة SQL. قم بإنشاء دليل باسم sql
وبداخله ملف باسم init.sql
بالمحتوى التالي لإعداد الجداول اللازمة:
-- Create database if not exists CREATE DATABASE IF NOT EXISTS digital_credentials; USE digital_credentials; -- Table for storing challenges CREATE TABLE IF NOT EXISTS challenges ( id VARCHAR(36) PRIMARY KEY, challenge VARCHAR(255) NOT NULL UNIQUE, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, used BOOLEAN DEFAULT FALSE, INDEX idx_challenge (challenge), INDEX idx_expires_at (expires_at) ); -- Table for storing verification sessions CREATE TABLE IF NOT EXISTS verification_sessions ( id VARCHAR(36) PRIMARY KEY, challenge_id VARCHAR(36), status ENUM('pending', 'verified', 'failed', 'expired') DEFAULT 'pending', presentation_data JSON, verified_at TIMESTAMP NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (challenge_id) REFERENCES challenges(id) ON DELETE CASCADE, INDEX idx_challenge_id (challenge_id), INDEX idx_status (status) ); -- Table for storing verified credentials data (optional) CREATE TABLE IF NOT EXISTS verified_credentials ( id VARCHAR(36) PRIMARY KEY, session_id VARCHAR(36), credential_type VARCHAR(255), issuer VARCHAR(255), subject VARCHAR(255), claims JSON, verified_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (session_id) REFERENCES verification_sessions(id) ON DELETE CASCADE, INDEX idx_session_id (session_id), INDEX idx_credential_type (credential_type) );
بمجرد وضع كلا الملفين في مكانهما، افتح الطرفية في جذر المشروع وقم بتشغيل:
docker-compose up -d
سيقوم هذا الأمر ببدء حاوية MySQL في الخلفية.
تم تصميم تطبيق Next.js الخاص بنا لفصل الاهتمامات بين الواجهة الأمامية والخلفية، على الرغم من أنهما جزء من نفس المشروع.
src/app/page.tsx
): صفحة React واحدة تبدأ
تدفق التحقق وتعرض النتيجة. تتفاعل مع واجهة برمجة تطبيقات بيانات الاعتماد الرقمية
للمتصفح.src/app/api/verify/...
):
start/route.ts
: يولد طلب OpenID4VP و nonce أمني.finish/route.ts
: يستقبل العرض من المحفظة (عبر المتصفح)، ويتحقق من صحة nonce، ويفك
تشفير بيانات الاعتماد.src/lib/
):
database.ts
: يدير جميع تفاعلات قاعدة البيانات (إنشاء التحديات، التحقق من الجلسات).crypto.ts
: يعالج فك تشفير بيانات اعتماد mDoc المستندة إلى CBOR.إليكم رسم تخطيطي يوضح البنية الداخلية:
واجهتنا الأمامية خفيفة الوزن بشكل مقصود. مسؤوليتها الأساسية هي أن تكون بمثابة المشغل المواجه للمستخدم لتدفق التحقق والتواصل مع كل من الواجهة الخلفية لدينا وقدرات معالجة بيانات الاعتماد الأصلية للمتصفح. لا تحتوي على أي منطق بروتوكول معقد بنفسها؛ كل ذلك تم تفويضه.
على وجه التحديد، ستتعامل الواجهة الأمامية مع ما يلي:
/api/verify/start
ويستقبل حمولة JSON
منظمة (protocol
، request
، state
) تصف بالضبط ما يجب على المحفظة تقديمه.navigator.credentials.get()
، الذي يعرض رمز QR أصليًا وينتظر استجابة المحفظة./api/verify/finish
الخاصة
بنا في طلب POST للتحقق النهائي من جانب الخادم.المنطق الأساسي موجود في دالة startVerification
:
// src/app/page.tsx const startVerification = async () => { setLoading(true); setVerificationResult(null); try { // 1. تحقق مما إذا كان المتصفح يدعم واجهة برمجة التطبيقات if (!navigator.credentials?.get) { throw new Error("Browser does not support the Credential API."); } // 2. اطلب من الواجهة الخلفية كائن طلب const res = await fetch("/api/verify/start"); const { protocol, request } = await res.json(); // 3. قم بتسليم هذا الكائن إلى المتصفح - وهذا يؤدي إلى تشغيل رمز الاستجابة السريعة الأصلي const credential = await (navigator.credentials as any).get({ mediation: "required", digital: { requests: [ { protocol, // "openid4vp" data: request, // contains dcql_query, nonce, etc. }, ], }, }); // 4. أعد توجيه استجابة المحفظة (من المتصفح) إلى نقطة النهاية النهائية لدينا لإجراء فحوصات من جانب الخادم const verifyRes = await fetch("/api/verify/finish", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(credential), }); const result = await verifyRes.json(); if (verifyRes.ok && result.verified) { setVerificationResult(`Success: ${result.message}`); } else { throw new Error(result.message || "Verification failed."); } } catch (err) { setVerificationResult(`Error: ${(err as Error).message}`); } finally { setLoading(false); } };
توضح هذه الدالة الخطوات الرئيسية الأربع لمنطق الواجهة الأمامية: التحقق من دعم واجهة برمجة التطبيقات، وجلب الطلب من الواجهة الخلفية، واستدعاء واجهة برمجة تطبيقات المتصفح، وإرسال النتيجة مرة أخرى للتحقق. بقية الملف هو كود React قياسي لإدارة الحالة وعرض واجهة المستخدم، والذي يمكنك عرضه في مستودع GitHub.
digital
و mediation: 'required'
؟#قد تلاحظ أن استدعائنا لـ navigator.credentials.get()
يبدو مختلفًا عن الأمثلة الأبسط. هذا
لأننا نلتزم بصرامة بمواصفات
W3C Digital Credentials API
الرسمية.
digital
Member: تتطلب المواصفات أن تكون جميع طلبات بيانات الاعتماد الرقمية متداخلة
داخل كائن digital
. يوفر هذا مساحة اسم واضحة وموحدة لواجهة برمجة التطبيقات هذه، مما
يميزها عن أنواع بيانات الاعتماد الأخرى (مثل password
أو federated
) ويسمح بالتوسعات
المستقبلية دون تعارضات.
mediation: 'required'
: هذا الخيار هو ميزة أمان وتجربة مستخدم حاسمة. يفرض أن يتفاعل
المستخدم بنشاط مع مطالبة (على سبيل المثال، مسح بيومتري، إدخال رقم التعريف الشخصي، أو
شاشة موافقة) للموافقة على طلب بيانات الاعتماد. بدونه، يمكن لموقع ويب محاولة الوصول إلى
بيانات الاعتماد بصمت في الخلفية، مما يشكل خطرًا كبيرًا على الخصوصية. من خلال طلب
الوساطة، نضمن أن المستخدم دائمًا في السيطرة ويعطي موافقة صريحة على كل معاملة.
مع وجود واجهة مستخدم React في مكانها، نحتاج الآن إلى مسارين API يقومان بالعمل الشاق على الخادم:
/api/verify/start
– يبني طلب OpenID4VP، ويحتفظ بتحدي لمرة واحدة في MySQL ويعيد كل
شيء إلى المتصفح./api/verify/finish
– يستقبل استجابة المحفظة، ويتحقق من صحة التحدي، ويتحقق من ويفك
تشفير بيانات الاعتماد، وأخيرًا يعيد نتيجة JSON موجزة إلى واجهة المستخدم./api/verify/start
: إنشاء طلب OpenID4VP#// src/app/api/verify/start/route.ts import { NextResponse } from "next/server"; import { v4 as uuidv4 } from "uuid"; import { createChallenge, cleanupExpiredChallenges } from "@/lib/database"; export async function GET() { // 1️⃣ أنشئ nonce عشوائيًا قصير العمر (تحدي) const challenge = uuidv4(); const challengeId = uuidv4(); const expiresAt = new Date(Date.now() + 5 * 60 * 1000); await createChallenge(challengeId, challenge, expiresAt); cleanupExpiredChallenges().catch(console.error); // 2️⃣ أنشئ استعلام DCQL يصف *ما* نريده const dcqlQuery = { credentials: [ { id: "cred1", format: "mso_mdoc", meta: { doctype_value: "eu.europa.ec.eudi.pid.1" }, claims: [ { path: ["eu.europa.ec.eudi.pid.1", "family_name"] }, { path: ["eu.europa.ec.eudi.pid.1", "given_name"] }, { path: ["eu.europa.ec.eudi.pid.1", "birth_date"] }, ], }, ], }; // 3️⃣ أعد كائنًا يمكن للمتصفح تمريره إلى navigator.credentials.get() return NextResponse.json({ protocol: "openid4vp", // يخبر المتصفح ببروتوكول المحفظة الذي يجب استخدامه request: { dcql_query: dcqlQuery, // ماذا يجب تقديمه nonce: challenge, // مضاد لإعادة التشغيل response_type: "vp_token", response_mode: "dc_api", // ستقوم المحفظة بـ POST مباشرة إلى /finish }, state: { credential_type: "mso_mdoc", // يتم الاحتفاظ به للتحقق لاحقًا nonce: challenge, challenge_id: challengeId, }, }); }
المعلمات الرئيسية
• nonce
– تحدي تشفيري يربط الطلب والاستجابة
(يمنع إعادة التشغيل). • dcql_query
– كائن يصف الادعاءات الدقيقة التي نحتاجها. في
هذا الدليل، نستخدم بنية dcql_query
مستوحاة من المسودات الحديثة للغة استعلام بيانات
الاعتماد الرقمية، على الرغم من أن هذا ليس معيارًا نهائيًا بعد. • state
– JSON
عشوائي يعاد من قبل المحفظة حتى نتمكن من البحث عن سجل قاعدة البيانات.
يغلف ملف src/lib/database.ts
عمليات MySQL الأساسية للتحديات وجلسات التحقق (إدراج، قراءة،
وضع علامة على أنه مستخدم). إن إبقاء هذا المنطق في وحدة واحدة يجعل من السهل تبديل مخزن
البيانات لاحقًا.
/api/verify/finish
: التحقق من صحة العرض وفك تشفيره#// src/app/api/verify/finish/route.ts import { NextResponse, NextRequest } from "next/server"; import { v4 as uuidv4 } from "uuid"; import { getChallenge, markChallengeAsUsed, createVerificationSession, updateVerificationSession, } from "@/lib/database"; import { decodeDigitalCredential, decodeAllNamespaces } from "@/lib/crypto"; export async function POST(request: NextRequest) { const body = await request.json(); // 1️⃣ استخراج أجزاء العرض القابل للتحقق const vpTokenMap = body.vp_token ?? body.data?.vp_token; const state = body.state; const mdocToken = vpTokenMap?.cred1; // طلبنا هذا المعرف في dcqlQuery if (!vpTokenMap || !state || !mdocToken) { return NextResponse.json( { verified: false, message: "Malformed response" }, { status: 400 }, ); } // 2️⃣ التحقق من صحة التحدي لمرة واحدة const stored = await getChallenge(state.nonce); if (!stored) { return NextResponse.json( { verified: false, message: "Invalid or expired challenge" }, { status: 400 }, ); } const sessionId = uuidv4(); await createVerificationSession(sessionId, stored.id); // 3️⃣ فحوصات تشفير (زائفة) - استبدلها بالتحقق الحقيقي من mDL في الإنتاج // في تطبيق حقيقي، ستستخدم مكتبة مخصصة لإجراء تحقق تشفيري كامل // لتوقيع mdoc مقابل المفتاح العام لجهة الإصدار. const isValid = mdocToken.length > 0; if (!isValid) { await updateVerificationSession(sessionId, "failed", { reason: "mdoc validation failed", }); return NextResponse.json( { verified: false, message: "Credential validation failed" }, { status: 400 }, ); } // 4️⃣ فك تشفير حمولة DL المحمولة (mdoc) إلى JSON قابل للقراءة const decoded = await decodeDigitalCredential(mdocToken); const readable = decodeAllNamespaces(decoded)["eu.europa.ec.eudi.pid.1"]; await markChallengeAsUsed(state.nonce); await updateVerificationSession(sessionId, "verified", { readable }); return NextResponse.json({ verified: true, message: "mdoc credential verified successfully!", credentialData: readable, sessionId, }); }
الحقول الهامة في استجابة المحفظة
• vp_token
– خريطة تحمل كل بيانات اعتماد تعيدها المحفظة. في عرضنا التوضيحي، نسحب
vp_token.cred1
. • state
– صدى للكتلة التي قدمناها في /start
؛ يحتوي على nonce
حتى نتمكن من البحث عن سجل قاعدة البيانات. • mdocToken
– بنية CBOR مشفرة بـ
Base64URL تمثل ISO mDoc.
عندما يتلقى المُحقِّق بيانات اعتماد mdoc من المتصفح، تكون عبارة عن سلسلة Base64URL تحتوي
على بيانات ثنائية مشفرة بـ CBOR. لاستخراج الادعاءات الفعلية، تقوم نقطة نهاية finish
بإجراء عملية فك تشفير متعددة الخطوات باستخدام دوال مساعدة من src/lib/crypto.ts
.
تتعامل دالة decodeDigitalCredential
مع التحويل من السلسلة المشفرة إلى كائن قابل
للاستخدام:
// src/lib/crypto.ts export async function decodeDigitalCredential(encodedCredential: string) { // 1. تحويل Base64URL إلى Base64 القياسي const base64UrlToBase64 = (input: string) => { let base64 = input.replace(/-/g, "+").replace(/_/g, "/"); const pad = base64.length % 4; if (pad) base64 += "=".repeat(4 - pad); return base64; }; const base64 = base64UrlToBase64(encodedCredential); // 2. فك تشفير Base64 إلى ثنائي const binaryString = atob(base64); const byteArray = Uint8Array.from(binaryString, (char) => char.charCodeAt(0)); // 3. فك تشفير CBOR const decoded = await cbor.decodeFirst(byteArray); return decoded; }
cbor-web
لفك تشفير البيانات الثنائية إلى كائن
JavaScript منظم.تقوم دالة decodeAllNamespaces
بمعالجة كائن CBOR المفكك بشكل أكبر لاستخراج الادعاءات
الفعلية من مساحات الأسماء ذات الصلة:
// src/lib/crypto.ts export function decodeAllNamespaces(jsonObj) { const decoded = {}; try { jsonObj.documents.forEach((doc, idx) => { // 1) issuerSigned.nameSpaces: const issuerNS = doc.issuerSigned?.nameSpaces || {}; Object.entries(issuerNS).forEach(([nsName, entries]) => { if (!decoded[nsName]) decoded[nsName] = {}; (entries as any[]).forEach((entry) => { const bytes = Uint8Array.from(entry.value); const decodedEntry = cbor.decodeFirstSync(bytes); Object.assign(decoded[nsName], decodedEntry); }); }); // 2) deviceSigned.nameSpaces (if present): const deviceNS = doc.deviceSigned?.nameSpaces; if (deviceNS?.value?.data) { const bytes = Uint8Array.from(deviceNS.value); decoded[`deviceSigned_ns_${idx}`] = cbor.decodeFirstSync(bytes); } }); } catch (e) { console.error(e); } return decoded; }
eu.europa.ec.eudi.pid.1
) لاستخراج قيم
الادعاء الفعلية (مثل الاسم، تاريخ الميلاد، إلخ).بعد المرور بهذه الخطوات، تحصل نقطة نهاية finish على كائن قابل للقراءة يحتوي على الادعاءات من mdoc، على سبيل المثال:
{ "family_name": "Doe", "given_name": "John", "birth_date": "1990-01-01" }
تضمن هذه العملية أن يتمكن المُحقِّق من استخراج المعلومات الضرورية بشكل آمن وموثوق من بيانات اعتماد mdoc للعرض والمعالجة الإضافية.
تعيد نقطة نهاية finish كائن JSON صغيرًا إلى الواجهة الأمامية:
{ "verified": true, "message": "mdoc credential verified successfully!", "credentialData": { "family_name": "Doe", "given_name": "John", "birth_date": "1990-01-01" } }
تتلقى الواجهة الأمامية هذه الاستجابة في startVerification()
وتحتفظ بها ببساطة في حالة
React حتى نتمكن من عرض بطاقة تأكيد لطيفة أو عرض ادعاءات فردية – على سبيل المثال “مرحبًا،
جون دو (مواليد 1990-01-01)!”.
لديك الآن أداة تحقق كاملة وعاملة تستخدم قدرات معالجة بيانات الاعتماد الأصلية للمتصفح. إليك كيفية تشغيلها محليًا وما يمكنك القيام به لنقلها من إثبات مفهوم إلى تطبيق جاهز للإنتاج.
نسخ المستودع:
git clone https://github.com/corbado/digital-credentials-example.git cd digital-credentials-example
تثبيت التبعيات:
npm install
بدء قاعدة البيانات: تأكد من أن Docker يعمل على جهازك، ثم ابدأ حاوية MySQL:
docker-compose up -d
تشغيل التطبيق:
npm run dev
افتح متصفحك على http://localhost:3000
، ويجب أن ترى واجهة مستخدم المُحقِّق. يمكنك
الآن استخدام CMWallet لمسح رمز الاستجابة السريعة وإكمال تدفق التحقق.
يوفر هذا البرنامج التعليمي اللبنات الأساسية لأداة التحقق. لجعلها جاهزة للإنتاج، ستحتاج إلى تنفيذ العديد من الميزات الإضافية:
التحقق التشفيري الكامل: يستخدم التنفيذ الحالي فحصًا مؤقتًا (mdocToken.length > 0
).
في سيناريو حقيقي، يجب عليك إجراء تحقق تشفيري كامل لتوقيع mdoc مقابل المفتاح العام لـ
جهة الإصدار (على سبيل المثال، عن طريق حل DID الخاص بهم أو جلب شهادة
المفتاح العام الخاصة بهم). لمعايير حل DID، ارجع إلى
مواصفات W3C DID Resolution.
التحقق من إلغاء جهة الإصدار: يمكن إلغاء بيانات الاعتماد من قبل جهة الإصدار قبل تاريخ انتهاء صلاحيتها. يجب على أداة التحقق في الإنتاج التحقق من حالة بيانات الاعتماد عن طريق الاستعلام عن قائمة إلغاء أو نقطة نهاية حالة مقدمة من جهة الإصدار. توفر قائمة حالة بيانات الاعتماد القابلة للتحقق من W3C المعيار لقوائم إلغاء بيانات الاعتماد.
معالجة الأخطاء القوية والأمان: أضف معالجة شاملة للأخطاء، والتحقق من صحة الإدخال، وتقييد المعدل على نقاط نهاية API، وتأكد من أن جميع الاتصالات تتم عبر HTTPS (TLS) لحماية البيانات أثناء النقل. توفر إرشادات أمان OWASP API أفضل الممارسات الشاملة لأمان API.
دعم أنواع متعددة من بيانات الاعتماد: قم بتوسيع المنطق للتعامل مع قيم doctype
وتنسيقات بيانات اعتماد مختلفة إذا كنت تتوقع تلقي أكثر من مجرد بيانات اعتماد PID
للهوية الرقمية الأوروبية (EUDI). يوفر
نموذج بيانات بيانات الاعتماد القابلة للتحقق من W3C
مواصفات شاملة لتنسيق VC.
يركز هذا المثال بشكل مقصود على التدفق الأساسي بوساطة المتصفح لجعله سهل الفهم. تعتبر الموضوعات التالية خارج النطاق:
redirect_uri
أو تسجيل العميل الديناميكي.من خلال البناء على هذا الأساس ودمج هذه الخطوات التالية، يمكنك تطوير أداة تحقق قوية وآمنة قادرة على الثقة والتحقق من صحة بيانات الاعتماد الرقمية في تطبيقاتك الخاصة.
هذا كل شيء! بأقل من 250 سطرًا من TypeScript، لدينا الآن أداة تحقق شاملة تقوم بما يلي:
في الإنتاج، ستستبدل التحقق المؤقت بفحوصات ISO 18013-5 كاملة، وتضيف عمليات بحث عن إلغاء جهة الإصدار، وتقييد المعدل، وتسجيل التدقيق، وبالطبع، TLS شامل - لكن اللبنات الأساسية تظل كما هي تمامًا.
فيما يلي بعض الموارد والمواصفات والأدوات الرئيسية المستخدمة أو المشار إليها في هذا البرنامج التعليمي:
مستودع المشروع:
المواصفات الرئيسية:
الأدوات:
المكتبات:
Related Articles
Table of Contents