OpenID4VCI प्रोटोकॉल का उपयोग करके W3C वेरिफ़ाएबल क्रेडेंशियल जारीकर्ता बनाना सीखें। यह स्टेप-बाय-स्टेप गाइड आपको दिखाता है कि एक Next.js एप्लिकेशन कैसे बनाया जाए जो क्रिप्टोग्राफ़िक रूप से हस्ताक्षरित क्रेडेंशियल जारी करता है जो डिजिटल वॉलेट के साथ संगत
Amine
Created: August 20, 2025
Updated: August 21, 2025
See the original blog version in English here.
डिजिटल क्रेडेंशियल एक सुरक्षित और गोपनीयता-संरक्षण तरीके से पहचान और दावों को साबित करने का एक शक्तिशाली तरीका है। लेकिन यूजर्स को ये क्रेडेंशियल पहली बार में कैसे मिलते हैं? यहीं पर Issuer की भूमिका महत्वपूर्ण हो जाती है। एक Issuer एक विश्वसनीय इकाई है - जैसे कि एक सरकारी एजेंसी, एक विश्वविद्यालय, या एक बैंक - जो यूजर्स को डिजिटल रूप से हस्ताक्षरित क्रेडेंशियल बनाने और वितरित करने के लिए जिम्मेदार है।
यह गाइड डिजिटल क्रेडेंशियल Issuer बनाने के लिए एक व्यापक, स्टेप-बाय-स्टेप ट्यूटोरियल प्रदान करता है। हम OpenID for Verifiable Credential Issuance (OpenID4VCI) प्रोटोकॉल पर ध्यान केंद्रित करेंगे, जो एक आधुनिक मानक है जो यह परिभाषित करता है कि यूजर्स किसी Issuer से क्रेडेंशियल कैसे प्राप्त कर सकते हैं और उन्हें अपने डिजिटल वॉलेट में सुरक्षित रूप से संग्रहीत कर सकते हैं।
अंतिम परिणाम एक कार्यात्मक Next.js एप्लिकेशन होगा जो यह कर सकता है:
Recent Articles
आगे बढ़ने से पहले, दो संबंधित लेकिन अलग-अलग अवधारणाओं के बीच के अंतर को स्पष्ट करना महत्वपूर्ण है:
डिजिटल क्रेडेंशियल (सामान्य शब्द): यह एक व्यापक श्रेणी है जिसमें क्रेडेंशियल, प्रमाण पत्र, या अटेस्टेशन का कोई भी डिजिटल रूप शामिल है। इनमें सरल डिजिटल प्रमाण पत्र, बुनियादी डिजिटल बैज, या कोई भी इलेक्ट्रॉनिक रूप से संग्रहीत क्रेडेंशियल शामिल हो सकता है जिसमें क्रिप्टोग्राफ़िक सुरक्षा सुविधाएँ हो भी सकती हैं और नहीं भी।
वेरिफ़ाएबल क्रेडेंशियल (VCs - W3C स्टैंडर्ड): यह एक विशिष्ट प्रकार का डिजिटल क्रेडेंशियल है जो W3C वेरिफ़ाएबल क्रेडेंशियल डेटा मॉडल मानक का पालन करता है। वेरिफ़ाएबल क्रेडेंशियल क्रिप्टोग्राफ़िक रूप से हस्ताक्षरित, टैम्पर-एविडेंट और गोपनीयता-सम्मान करने वाले क्रेडेंशियल होते हैं जिन्हें स्वतंत्र रूप से सत्यापित किया जा सकता है। उनमें विशिष्ट तकनीकी आवश्यकताएं शामिल हैं जैसे:
इस गाइड में, हम विशेष रूप से एक वेरिफ़ाएबल क्रेडेंशियल जारीकर्ता का निर्माण कर रहे हैं जो W3C मानक का पालन करता है, न कि केवल किसी डिजिटल क्रेडेंशियल सिस्टम का। हम जिस OpenID4VCI प्रोटोकॉल का उपयोग कर रहे हैं, वह विशेष रूप से वेरिफ़ाएबल क्रेडेंशियल जारी करने के लिए डिज़ाइन किया गया है, और हम जो JWT-VC प्रारूप लागू करेंगे, वह वेरिफ़ाएबल क्रेडेंशियल के लिए W3C-अनुपालन प्रारूप है।
डिजिटल क्रेडेंशियल के पीछे का जादू एक सरल लेकिन शक्तिशाली "ट्रस्ट ट्रायंगल" मॉडल में निहित है जिसमें तीन प्रमुख खिलाड़ी शामिल हैं:
जारी करने का प्रवाह इस इकोसिस्टम में पहला कदम है। Issuer यूजर की जानकारी को मान्य करता है और उन्हें एक क्रेडेंशियल प्रदान करता है। एक बार जब Holder के पास यह क्रेडेंशियल उनके वॉलेट में होता है, तो वे अपनी पहचान या दावों को साबित करने के लिए इसे एक Verifier को प्रस्तुत कर सकते हैं, जिससे ट्रायंगल पूरा होता है।
यहाँ अंतिम एप्लिकेशन का एक त्वरित अवलोकन है:
चरण 1: यूजर डेटा इनपुट यूजर एक नया क्रेडेंशियल अनुरोध करने के लिए अपनी व्यक्तिगत जानकारी के साथ एक फ़ॉर्म भरता है।
चरण 2: क्रेडेंशियल ऑफ़र जेनरेशन एप्लिकेशन एक सुरक्षित क्रेडेंशियल ऑफ़र जेनरेट करता है, जो एक QR कोड और एक पूर्व-अधिकृत कोड के रूप में प्रदर्शित होता है।
चरण 3: वॉलेट इंटरैक्शन यूजर संगत वॉलेट (जैसे, Sphereon Wallet) के साथ QR कोड को स्कैन करता है और जारी करने को अधिकृत करने के लिए एक पिन दर्ज करता है।
चरण 4: क्रेडेंशियल जारी किया गया वॉलेट नए जारी किए गए डिजिटल क्रेडेंशियल को प्राप्त और संग्रहीत करता है, जो भविष्य के उपयोग के लिए तैयार है।
कोड में गोता लगाने से पहले, आइए उन मूलभूत ज्ञान और उपकरणों को कवर करें जिनकी आपको आवश्यकता होगी। यह गाइड मानता है कि आपको वेब विकास अवधारणाओं की बुनियादी जानकारी है, लेकिन एक क्रेडेंशियल जारीकर्ता बनाने के लिए निम्नलिखित आवश्यक शर्तें हैं।
हमारा Issuer खुले मानकों के एक सेट पर बनाया गया है जो वॉलेट और जारी करने वाली सेवाओं के बीच इंटरऑपरेबिलिटी सुनिश्चित करता है। इस ट्यूटोरियल के लिए, हम निम्नलिखित पर ध्यान केंद्रित करेंगे:
मानक / प्रोटोकॉल | विवरण |
---|---|
OpenID4VCI | वेरिफ़ाएबल क्रेडेंशियल जारी करने के लिए OpenID। यह मुख्य प्रोटोकॉल है जिसका हम उपयोग करेंगे। यह एक मानक प्रवाह को परिभाषित करता है कि कैसे एक यूजर (अपने वॉलेट के माध्यम से) एक Issuer से क्रेडेंशियल का अनुरोध और प्राप्त कर सकता है। |
JWT-VC | JWT-आधारित वेरिफ़ाएबल क्रेडेंशियल। हम जो क्रेडेंशियल जारी करेंगे उसका प्रारूप। यह एक W3C मानक है जो वेरिफ़ाएबल क्रेडेंशियल को JSON वेब टोकन (JWTs) के रूप में एन्कोड करता है, जिससे वे कॉम्पैक्ट और वेब-अनुकूल बनते हैं। |
ISO mDoc | ISO/IEC 18013-5। मोबाइल ड्राइवर लाइसेंस (mDLs) के लिए अंतर्राष्ट्रीय मानक। जबकि हम एक JWT-VC जारी करते हैं, इसके भीतर के claims mDoc डेटा मॉडल (जैसे, eu.europa.ec.eudi.pid.1 ) के साथ संगत होने के लिए संरचित हैं। |
OAuth 2.0 | OpenID4VCI द्वारा उपयोग किया जाने वाला अंतर्निहित प्राधिकरण ढांचा। हम एक pre-authorized_code प्रवाह लागू करेंगे, जो सुरक्षित और उपयोगकर्ता-अनुकूल क्रेडेंशियल जारी करने के लिए डिज़ाइन किया गया एक विशिष्ट अनुदान प्रकार है। |
OpenID4VCI क्रेडेंशियल जारी करने के लिए दो प्राथमिक प्राधिकरण प्रवाह का समर्थन करता है:
प्री-ऑथराइज्ड कोड फ्लो: इस फ्लो में, Issuer एक अल्पकालिक, एकल-उपयोग कोड
(pre-authorized_code
) उत्पन्न करता है जो यूजर के लिए तुरंत उपलब्ध होता है। यूजर का
वॉलेट तब इस कोड को सीधे क्रेडेंशियल के लिए एक्सचेंज कर सकता है। यह फ्लो उन परिदृश्यों
के लिए आदर्श है जहां यूजर पहले से ही Issuer की वेबसाइट पर प्रमाणित
और उपस्थित है, क्योंकि यह रीडायरेक्ट के बिना एक सहज, तत्काल जारी करने का अनुभव प्रदान
करता है।
ऑथराइजेशन कोड फ्लो: यह मानक OAuth 2.0 फ्लो है, जहां यूजर को
सहमति देने के लिए एक प्राधिकरण सर्वर पर रीडायरेक्ट किया जाता है। अनुमोदन के बाद, सर्वर
एक authorization_code
को एक पंजीकृत redirect_uri
पर वापस भेजता है। यह फ्लो तीसरे
पक्ष के एप्लिकेशन के लिए अधिक उपयुक्त है जो यूजर की ओर से जारी करने की प्रक्रिया शुरू
करते हैं।
इस ट्यूटोरियल के लिए, हम pre-authorized_code
फ्लो का उपयोग करेंगे। हमने यह दृष्टिकोण
चुना क्योंकि यह सरल है और हमारे विशिष्ट उपयोग के मामले के लिए एक अधिक सीधा यूजर अनुभव
प्रदान करता है: एक यूजर सीधे Issuer की अपनी वेबसाइट से एक क्रेडेंशियल
का अनुरोध कर रहा है। यह जटिल रीडायरेक्ट और क्लाइंट पंजीकरण की आवश्यकता को समाप्त करता है,
जिससे मुख्य जारी करने के तर्क को समझना और लागू करना आसान हो जाता है।
मानकों का यह संयोजन हमें एक ऐसा जारीकर्ता बनाने की अनुमति देता है जो डिजिटल वॉलेट की एक विस्तृत श्रृंखला के साथ संगत है और यूजर के लिए एक सुरक्षित, मानकीकृत प्रक्रिया सुनिश्चित करता है।
अपने जारीकर्ता को बनाने के लिए, हम उसी मजबूत और आधुनिक टेक स्टैक का उपयोग करेंगे जिसका हमने सत्यापनकर्ता के लिए उपयोग किया था, जिससे एक सुसंगत और उच्च-गुणवत्ता वाला डेवलपर अनुभव सुनिश्चित होता है।
हम अपने फ्रंटएंड और बैकएंड दोनों कोड के लिए TypeScript का उपयोग करेंगे। इसकी स्टैटिक टाइपिंग एक जारीकर्ता जैसे सुरक्षा-महत्वपूर्ण एप्लिकेशन में अमूल्य है, क्योंकि यह सामान्य त्रुटियों को रोकने में मदद करता है और कोड की समग्र गुणवत्ता और रखरखाव में सुधार करता है।
Next.js हमारी पसंद का फ्रेमवर्क है क्योंकि यह फुल-स्टैक एप्लिकेशन बनाने के लिए एक सहज, एकीकृत अनुभव प्रदान करता है।
हमारा कार्यान्वयन विशिष्ट कार्यों को संभालने के लिए कुछ प्रमुख लाइब्रेरीज़ पर निर्भर करेगा:
pre-authorized_code
मान बनाने के लिए करेंगे।अपने जारीकर्ता का परीक्षण करने के लिए, आपको एक मोबाइल वॉलेट की आवश्यकता होगी जो OpenID4VCI प्रोटोकॉल का समर्थन करता हो। इस ट्यूटोरियल के लिए, हम Sphereon Wallet की सलाह देते हैं, जो Android और iOS दोनों के लिए उपलब्ध है।
Sphereon Wallet कैसे इंस्टॉल करें:
एक क्रेडेंशियल जारी करना एक सुरक्षा-महत्वपूर्ण ऑपरेशन है जो विश्वास और प्रामाणिकता सुनिश्चित करने के लिए मौलिक क्रिप्टोग्राफ़िक अवधारणाओं पर निर्भर करता है।
अपने मूल में, एक वेरिफ़ाएबल क्रेडेंशियल दावों का एक सेट है जिसे Issuer द्वारा डिजिटल रूप से हस्ताक्षरित किया गया है। यह हस्ताक्षर दो गारंटी प्रदान करता है:
डिजिटल हस्ताक्षर पब्लिक/प्राइवेट की क्रिप्टोग्राफ़ी का उपयोग करके बनाए जाते हैं। यहाँ यह कैसे काम करता है:
हमारे कार्यान्वयन में, हम एक एलिप्टिक कर्व (EC) की-पेयर उत्पन्न करेंगे और JWT-VC पर
हस्ताक्षर करने के लिए ES256
एल्गोरिथ्म का उपयोग करेंगे। पब्लिक की Issuer के DID
(did:web
) में एम्बेडेड है, जिससे कोई भी Verifier इसे खोज सकता है और क्रेडेंशियल के
हस्ताक्षर को मान्य कर सकता है। नोट: हमारे JWTs में aud
(ऑडियंस) क्लेम को जानबूझकर
छोड़ दिया गया है, क्योंकि क्रेडेंशियल को सामान्य-उद्देश्य के लिए डिज़ाइन किया गया है और यह
किसी विशिष्ट वॉलेट से बंधा नहीं है। यदि आप किसी विशेष ऑडियंस के लिए उपयोग को प्रतिबंधित
करना चाहते हैं, तो एक aud
क्लेम शामिल करें और इसे तदनुसार सेट करें।
हमारा Issuer एप्लिकेशन एक फुल-स्टैक Next.js प्रोजेक्ट के रूप में बनाया गया है, जिसमें
फ्रंटएंड और बैकएंड लॉजिक के बीच एक स्पष्ट अलगाव है। यह आर्किटेक्चर हमें सर्वर पर सभी
सुरक्षा-महत्वपूर्ण कार्यों को संभालते हुए एक सहज यूजर अनुभव बनाने की अनुमति देता है।
महत्वपूर्ण: SQL में शामिल verification_sessions
और verified_credentials
टेबल इस
जारीकर्ता के लिए आवश्यक नहीं हैं, लेकिन पूर्णता के लिए शामिल की गई हैं।
src/app/issue/page.tsx
): एक एकल React पेज जो
यूजर्स को क्रेडेंशियल का अनुरोध करने के लिए अपना डेटा इनपुट करने की अनुमति देता है। यह
जारी करने की प्रक्रिया शुरू करने के लिए हमारे बैकएंड पर API कॉल करता है।src/app/api/issue/...
): सर्वर-साइड एंडपॉइंट्स का एक सेट जो
OpenID4VCI प्रोटोकॉल को लागू करता है।
/.well-known/openid-credential-issuer
: एक सार्वजनिक मेटाडेटा एंडपॉइंट। यह पहला URL
है जिसे एक वॉलेट जारीकर्ता की क्षमताओं की खोज के लिए जांचेगा, जिसमें उसका प्राधिकरण
सर्वर, टोकन एंडपॉइंट, क्रेडेंशियल एंडपॉइंट, और उन क्रेडेंशियल्स के प्रकार शामिल हैं
जो यह प्रदान करता है।/.well-known/openid-configuration
: एक मानक OpenID कनेक्ट डिस्कवरी एंडपॉइंट। जबकि
ऊपर वाले से निकटता से संबंधित है, यह एंडपॉइंट व्यापक OIDC-संबंधित कॉन्फ़िगरेशन
परोसता है और अक्सर मानक OpenID क्लाइंट के साथ इंटरऑपरेबिलिटी के लिए आवश्यक होता है।/.well-known/did.json
: हमारे जारीकर्ता के लिए DID दस्तावेज़। did:web
विधि का
उपयोग करते समय, इस फ़ाइल का उपयोग जारीकर्ता की सार्वजनिक कुंजी प्रकाशित करने के लिए
किया जाता है, जिसका उपयोग सत्यापनकर्ता इसके द्वारा जारी किए गए क्रेडेंशियल के
हस्ताक्षर को मान्य करने के लिए कर सकते हैं।authorize/route.ts
: एक pre-authorized_code
और एक क्रेडेंशियल ऑफ़र बनाता है।token/route.ts
: pre-authorized_code
को एक एक्सेस टोकन
के लिए एक्सचेंज करता है।credential/route.ts
: अंतिम, क्रिप्टोग्राफ़िक रूप से हस्ताक्षरित JWT-VC जारी करता
है।schemas/pid/route.ts
: PID क्रेडेंशियल के लिए JSON स्कीमा को उजागर करता है। यह
क्रेडेंशियल के किसी भी उपभोक्ता को इसकी संरचना और डेटा प्रकारों को समझने की अनुमति
देता है।src/lib/
):
database.ts
: सभी डेटाबेस इंटरैक्शन का प्रबंधन करता है, जैसे प्राधिकरण कोड और
जारीकर्ता कुंजी संग्रहीत करना।crypto.ts
: सभी क्रिप्टोग्राफ़िक कार्यों को संभालता है, जिसमें कुंजी पीढ़ी और JWT
हस्ताक्षर शामिल हैं।यहाँ जारी करने के प्रवाह को दर्शाने वाला एक आरेख है:
अब जब हमें मानकों, प्रोटोकॉल और आर्किटेक्चर की ठोस समझ हो गई है, तो हम अपने जारीकर्ता का निर्माण शुरू कर सकते हैं।
साथ चलें या अंतिम कोड का उपयोग करें
अब हम सेटअप और कोड कार्यान्वयन के माध्यम से स्टेप-बाय-स्टेप चलेंगे। यदि आप सीधे तैयार उत्पाद पर जाना पसंद करते हैं, तो आप हमारे GitHub रिपॉजिटरी से पूर्ण प्रोजेक्ट को क्लोन कर सकते हैं और इसे स्थानीय रूप से चला सकते हैं।
git clone https://github.com/corbado/digital-credentials-example.git
सबसे पहले, हम एक नया Next.js प्रोजेक्ट शुरू करेंगे, आवश्यक निर्भरताएँ स्थापित करेंगे, और अपना डेटाबेस शुरू करेंगे।
अपना टर्मिनल खोलें, उस डायरेक्टरी में नेविगेट करें जहाँ आप अपना प्रोजेक्ट बनाना चाहते हैं, और निम्नलिखित कमांड चलाएँ। हम इस प्रोजेक्ट के लिए ऐप राउटर, TypeScript, और Tailwind CSS का उपयोग कर रहे हैं।
npx create-next-app@latest . --ts --eslint --tailwind --app --src-dir --import-alias "@/*" --use-npm
यह कमांड आपकी वर्तमान डायरेक्टरी में एक नया Next.js एप्लिकेशन बनाता है।
इसके बाद, हमें उन लाइब्रेरीज़ को स्थापित करने की आवश्यकता है जो JWTs, डेटाबेस कनेक्शन और UUID पीढ़ी को संभालेंगी।
npm install jose mysql2 uuid @types/uuid
यह कमांड इंस्टॉल करता है:
jose
: JSON वेब टोकन (JWTs) पर हस्ताक्षर करने और सत्यापित करने के लिए।mysql2
: हमारे डेटाबेस के लिए MySQL क्लाइंट।uuid
: अद्वितीय चुनौती स्ट्रिंग उत्पन्न करने के लिए।@types/uuid
: uuid
लाइब्रेरी के लिए TypeScript प्रकार।हमारे बैकएंड को प्राधिकरण कोड, जारी करने के सत्र और जारीकर्ता कुंजी संग्रहीत करने के लिए
एक MySQL डेटाबेस की आवश्यकता होती है। हमने इसे
आसान बनाने के लिए एक 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) ); -- ISSUER TABLES -- Table for storing authorization codes in OpenID4VCI flow CREATE TABLE IF NOT EXISTS authorization_codes ( id VARCHAR(36) PRIMARY KEY, code VARCHAR(255) NOT NULL UNIQUE, client_id VARCHAR(255), scope VARCHAR(255), code_challenge VARCHAR(255), code_challenge_method VARCHAR(50), redirect_uri TEXT, user_pin VARCHAR(10), expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, used BOOLEAN DEFAULT FALSE, INDEX idx_code (code), INDEX idx_expires_at (expires_at) ); -- Table for storing issuance sessions CREATE TABLE IF NOT EXISTS issuance_sessions ( id VARCHAR(36) PRIMARY KEY, authorization_code_id VARCHAR(36), access_token VARCHAR(255), token_type VARCHAR(50) DEFAULT 'Bearer', expires_in INT DEFAULT 3600, c_nonce VARCHAR(255), c_nonce_expires_at TIMESTAMP, status ENUM('pending', 'authorized', 'credential_issued', 'expired', 'failed') DEFAULT 'pending', user_data JSON, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (authorization_code_id) REFERENCES authorization_codes(id) ON DELETE CASCADE, INDEX idx_access_token (access_token), INDEX idx_c_nonce (c_nonce), INDEX idx_status (status) ); -- Table for storing issued credentials CREATE TABLE IF NOT EXISTS issued_credentials ( id VARCHAR(36) PRIMARY KEY, session_id VARCHAR(36), credential_id VARCHAR(255), credential_type VARCHAR(255) DEFAULT 'jwt_vc', doctype VARCHAR(255) DEFAULT 'eu.europa.ec.eudi.pid.1', credential_data LONGTEXT, -- Base64 encoded mDoc credential_claims JSON, issuer_did VARCHAR(255), subject_id VARCHAR(255), issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP, revoked BOOLEAN DEFAULT FALSE, revoked_at TIMESTAMP NULL, FOREIGN KEY (session_id) REFERENCES issuance_sessions(id) ON DELETE CASCADE, INDEX idx_credential_id (credential_id), INDEX idx_session_id (session_id), INDEX idx_doctype (doctype), INDEX idx_subject_id (subject_id), INDEX idx_issued_at (issued_at) ); -- Table for storing issuer keys (simplified for demo) CREATE TABLE IF NOT EXISTS issuer_keys ( id VARCHAR(36) PRIMARY KEY, key_id VARCHAR(255) NOT NULL UNIQUE, key_type VARCHAR(50) NOT NULL, -- 'EC', 'RSA' algorithm VARCHAR(50) NOT NULL, -- 'ES256', 'RS256', etc. public_key TEXT NOT NULL, -- JWK format private_key TEXT NOT NULL, -- JWK format (encrypted in production) is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_key_id (key_id), INDEX idx_is_active (is_active) );
एक बार दोनों फ़ाइलें जगह पर हों, तो अपने प्रोजेक्ट रूट में अपना टर्मिनल खोलें और चलाएँ:
docker-compose up -d
यह कमांड बैकग्राउंड में एक MySQL कंटेनर शुरू करेगा, जो हमारे एप्लिकेशन के उपयोग के लिए तैयार है।
API एंडपॉइंट बनाने से पहले, आइए साझा लाइब्रेरीज़ बनाएं जो कोर बिज़नेस लॉजिक को संभालेंगी। यह दृष्टिकोण हमारे API रूट्स को साफ और HTTP अनुरोधों को संभालने पर केंद्रित रखता है, जबकि जटिल काम इन मॉड्यूल को सौंप दिया जाता है।
src/lib/database.ts
)#यह फ़ाइल सभी डेटाबेस इंटरैक्शन के लिए सत्य का एकल स्रोत है। यह हमारे MySQL कंटेनर से
कनेक्ट करने के लिए mysql2
लाइब्रेरी का उपयोग करती है और हमारी तालिकाओं में रिकॉर्ड
बनाने, पढ़ने और अपडेट करने के लिए निर्यात किए गए कार्यों का एक सेट प्रदान करती है। यह
एब्स्ट्रैक्शन लेयर हमारे कोड को अधिक मॉड्यूलर और बनाए रखने में आसान बनाती है।
src/lib/database.ts
फ़ाइल को निम्नलिखित सामग्री के साथ बनाएं:
// src/lib/database.ts import mysql from "mysql2/promise"; // Database connection configuration const dbConfig = { host: process.env.DATABASE_HOST || "localhost", port: parseInt(process.env.DATABASE_PORT || "3306"), user: process.env.DATABASE_USER || "app_user", password: process.env.DATABASE_PASSWORD || "app_password", database: process.env.DATABASE_NAME || "digital_credentials", timezone: "+00:00", }; let connection: mysql.Connection | null = null; export async function getConnection(): Promise<mysql.Connection> { if (!connection) { connection = await mysql.createConnection(dbConfig); } return connection; } // Data-Access-Object (DAO) functions for each table // ... (e.g., createChallenge, getChallenge, createAuthorizationCode, etc.)
नोट: संक्षिप्तता के लिए, DAO कार्यों की पूरी सूची को छोड़ दिया गया है। आप पूरा कोड प्रोजेक्ट रिपॉजिटरी में पा सकते हैं। इस फ़ाइल में चुनौतियों, सत्यापन सत्रों, प्राधिकरण कोड, जारी करने के सत्रों और जारीकर्ता कुंजी के प्रबंधन के लिए कार्य शामिल हैं।
src/lib/crypto.ts
)#यह फ़ाइल सभी सुरक्षा-महत्वपूर्ण क्रिप्टोग्राफ़िक कार्यों को संभालती है। यह की-पेयर जेनरेट
करने और JSON वेब टोकन (JWTs) पर हस्ताक्षर करने के लिए jose
लाइब्रेरी का उपयोग करती है।
की जेनरेशन generateIssuerKeyPair
फ़ंक्शन एक नया एलिप्टिक कर्व की-पेयर बनाता है जिसका
उपयोग क्रेडेंशियल पर हस्ताक्षर करने के लिए किया जाएगा। पब्लिक की JSON वेब की (JWK) प्रारूप
में निर्यात की जाती है ताकि इसे हमारे did.json
दस्तावेज़ में प्रकाशित किया जा सके।
// src/lib/crypto.ts import { generateKeyPair, exportJWK, SignJWT } from "jose"; export async function generateIssuerKeyPair(keyId: string, issuerDid: string) { const { publicKey, privateKey } = await generateKeyPair("ES256", { crv: "P-256", extractable: true, }); const publicKeyJWK = await exportJWK(publicKey); publicKeyJWK.kid = keyId; // Assign a unique key ID // ... (private key export and other setup) return { publicKey, privateKey, publicKeyJWK /* ... */ }; }
JWT क्रेडेंशियल निर्माण createJWTVerifiableCredential
फ़ंक्शन जारी करने की प्रक्रिया
का मूल है। यह यूजर के दावों, जारीकर्ता के की-पेयर, और अन्य मेटाडेटा को लेता है, और उनका
उपयोग एक हस्ताक्षरित JWT-VC बनाने के लिए करता है।
// src/lib/crypto.ts export async function createJWTVerifiableCredential( claims: MDocClaims, issuerKeyPair: IssuerKeyPair, subjectId: string, audience: string, ): Promise<string> { const now = Math.floor(Date.now() / 1000); const oneYear = 365 * 24 * 60 * 60; const vcPayload = { // The issuer's DID iss: issuerKeyPair.issuerDid, // The subject's (holder's) DID sub: subjectId, // The time the credential was issued (iat) and when it expires (exp) iat: now, exp: now + oneYear, // The Verifiable Credential data model vc: { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://europa.eu/eudi/pid/v1", ], type: ["VerifiableCredential", "eu.europa.ec.eudi.pid.1"], issuer: issuerKeyPair.issuerDid, issuanceDate: new Date(now * 1000).toISOString(), credentialSubject: { id: subjectId, ...claims, }, }, }; // Sign the payload with the issuer's private key return await new SignJWT(vcPayload) .setProtectedHeader({ alg: issuerKeyPair.algorithm, kid: issuerKeyPair.keyId, typ: "JWT", }) .sign(issuerKeyPair.privateKey); }
यह फ़ंक्शन W3C वेरिफ़ाएबल क्रेडेंशियल डेटा मॉडल के अनुसार JWT पेलोड का निर्माण करता है और इसे जारीकर्ता की निजी कुंजी के साथ हस्ताक्षरित करता है, जिससे एक सुरक्षित और वेरिफ़ाएबल क्रेडेंशियल बनता है।
हमारा Next.js एप्लिकेशन फ्रंटएंड और बैकएंड के बीच चिंताओं को अलग करने के लिए संरचित है, भले ही वे एक ही प्रोजेक्ट का हिस्सा हों। यह UI पेजों और API एंडपॉइंट्स दोनों के लिए ऐप राउटर का लाभ उठाकर हासिल किया जाता है।
फ्रंटएंड (src/app/issue/page.tsx
): एक एकल React पेज
कंपोनेंट जो /issue
रूट के लिए UI को परिभाषित करता है। यह यूजर इनपुट को संभालता है और
हमारे बैकएंड API के साथ संचार करता है।
बैकएंड API रूट्स (src/app/api/...
):
.well-known/.../route.ts
): ये रूट्स सार्वजनिक मेटाडेटा एंडपॉइंट्स को
उजागर करते हैं जो वॉलेट और अन्य क्लाइंट को जारीकर्ता की क्षमताओं और सार्वजनिक कुंजी
की खोज करने की अनुमति देते हैं।issue/.../route.ts
): ये एंडपॉइंट्स कोर OpenID4VCI लॉजिक को लागू
करते हैं, जिसमें क्रेडेंशियल ऑफ़र बनाना, टोकन जारी करना और अंतिम क्रेडेंशियल पर
हस्ताक्षर करना शामिल है।schemas/pid/route.ts
): यह रूट क्रेडेंशियल के लिए JSON स्कीमा परोसता
है, इसकी संरचना को परिभाषित करता है।लाइब्रेरी (src/lib/
): इस डायरेक्टरी में बैकएंड में साझा किया गया पुन: प्रयोज्य
लॉजिक होता है।
database.ts
: सभी डेटाबेस इंटरैक्शन का प्रबंधन करता है, SQL क्वेरी को एब्स्ट्रैक्ट
करता है।crypto.ts
: सभी क्रिप्टोग्राफ़िक कार्यों को संभालता है, जैसे की जेनरेशन और JWT
हस्ताक्षर।यह स्पष्ट अलगाव एप्लिकेशन को मॉड्यूलर और बनाए रखने में आसान बनाता है।
नोट: generateIssuerDid()
फ़ंक्शन को आपके जारीकर्ता डोमेन से मेल खाने वाला एक मान्य
did:web
लौटाना होगा। तैनात होने पर, .well-known/did.json
को उस डोमेन पर HTTPS पर परोसा
जाना चाहिए ताकि सत्यापनकर्ता क्रेडेंशियल को मान्य कर सकें।
हमारा फ्रंटएंड एक एकल React पेज है जो यूजर्स को एक नया डिजिटल क्रेडेंशियल अनुरोध करने के लिए एक सरल फ़ॉर्म प्रदान करता है। इसकी जिम्मेदारियां हैं:
कोर लॉजिक handleSubmit
फ़ंक्शन में संभाला जाता है, जो तब ट्रिगर होता है जब यूजर फ़ॉर्म
सबमिट करता है।
// src/app/issue/page.tsx const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); setError(null); setCredentialOffer(null); try { // 1. Validate required fields if (!userData.given_name || !userData.family_name || !userData.birth_date) { throw new Error("Please fill in all required fields"); } // 2. Request a credential offer from the backend const response = await fetch("/api/issue/authorize", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ user_data: userData, }), }); if (!response.ok) { const errorData = await response.json(); throw new Error( errorData.error_description || "Failed to create credential offer", ); } // 3. Set the credential offer in state to display the QR code const result = await response.json(); setCredentialOffer(result); } catch (err) { const errorMessage = (err as Error).message || "Unknown error occurred"; setError(errorMessage); } finally { setLoading(false); } };
यह फ़ंक्शन तीन प्रमुख क्रियाएं करता है:
/api/issue/authorize
एंडपॉइंट पर यूजर के डेटा के साथ एक POST
अनुरोध भेजता
है।फ़ाइल के बाकी हिस्से में फ़ॉर्म और QR कोड डिस्प्ले को रेंडर करने के लिए मानक React कोड होता है। आप पूरी फ़ाइल प्रोजेक्ट रिपॉजिटरी में देख सकते हैं।
बैकएंड API बनाने से पहले, हमें अपने पर्यावरण को कॉन्फ़िगर करने और डिस्कवरी एंडपॉइंट्स
स्थापित करने की आवश्यकता है। ये .well-known
फ़ाइलें वॉलेट के लिए हमारे जारीकर्ता को
खोजने और उसके साथ कैसे इंटरैक्ट करना है, यह समझने के लिए महत्वपूर्ण हैं।
अपने प्रोजेक्ट के रूट में .env.local
नामक एक फ़ाइल बनाएँ और निम्नलिखित पंक्ति जोड़ें। एक
मोबाइल वॉलेट तक पहुंचने के लिए यह URL सार्वजनिक रूप से सुलभ होना चाहिए। स्थानीय विकास के
लिए, आप अपने localhost
को उजागर करने के लिए
ngrok जैसी टनलिंग सेवा का उपयोग कर सकते
हैं।
NEXT_PUBLIC_BASE_URL=http://localhost:3000
वॉलेट मानक .well-known
URLs को क्वेरी करके एक जारीकर्ता की क्षमताओं की खोज करते हैं।
हमें इनमें से तीन एंडपॉइंट बनाने की आवश्यकता है।
1. जारीकर्ता मेटाडेटा (/.well-known/openid-credential-issuer
)
यह OpenID4VCI के लिए प्राथमिक डिस्कवरी फ़ाइल है। यह वॉलेट को जारीकर्ता के बारे में वह सब कुछ बताती है जो उसे जानने की जरूरत है, जिसमें उसके एंडपॉइंट्स, वह किस प्रकार के क्रेडेंशियल प्रदान करता है, और समर्थित क्रिप्टोग्राफ़िक एल्गोरिदम शामिल हैं।
फ़ाइल src/app/.well-known/openid-credential-issuer/route.ts
बनाएं:
// src/app/.well-known/openid-credential-issuer/route.ts import { NextResponse } from "next/server"; export async function GET() { const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"; const issuerMetadata = { // The issuer's unique identifier. issuer: baseUrl, // The URL of the authorization server. For simplicity, our issuer is its own authorization server. authorization_servers: [baseUrl], // The URL of the credential issuer. credential_issuer: baseUrl, // The endpoint where the wallet will POST to receive the actual credential. credential_endpoint: `${baseUrl}/api/issue/credential`, // The endpoint where the wallet exchanges an authorization code for an access token. token_endpoint: `${baseUrl}/api/issue/token`, // The endpoint for the authorization flow (not used in our pre-authorized flow, but good practice to include). authorization_endpoint: `${baseUrl}/api/issue/authorize`, // Indicates support for the pre-authorized code flow without requiring client authentication. pre_authorized_grant_anonymous_access_supported: true, // Human-readable information about the issuer. display: [ { name: "Corbado Credentials Issuer", locale: "en-US", }, ], // A list of the credential types this issuer can issue. credential_configurations_supported: { "eu.europa.ec.eudi.pid.1": { // The format of the credential (e.g., jwt_vc, mso_mdoc). format: "jwt_vc", // The specific document type, conforming to ISO mDoc standards. doctype: "eu.europa.ec.eudi.pid.1", // The OAuth 2.0 scope associated with this credential type. scope: "eu.europa.ec.eudi.pid.1", // Methods the wallet can use to prove possession of its key. cryptographic_binding_methods_supported: ["jwk"], // Signing algorithms the issuer supports for this credential. credential_signing_alg_values_supported: ["ES256"], // Proof-of-possession types the wallet can use. proof_types_supported: { jwt: { proof_signing_alg_values_supported: ["ES256", "ES384", "ES512"], }, }, // Display properties for the credential. display: [ { name: "Corbado Credential Issuer", locale: "en-US", logo: { uri: `${baseUrl}/logo.png`, alt_text: "EU Digital Identity", }, background_color: "#003399", text_color: "#FFFFFF", }, ], // A list of the claims (attributes) in the credential. claims: { "eu.europa.ec.eudi.pid.1": { given_name: { mandatory: true, display: [{ name: "Given Name", locale: "en-US" }], }, family_name: { mandatory: true, display: [{ name: "Family Name", locale: "en-US" }], }, birth_date: { mandatory: true, display: [{ name: "Date of Birth", locale: "en-US" }], }, }, }, }, }, // Authentication methods supported by the token endpoint. 'none' means public client. token_endpoint_auth_methods_supported: ["none"], // PKCE code challenge methods supported. code_challenge_methods_supported: ["S256"], // OAuth 2.0 grant types the issuer supports. grant_types_supported: [ "authorization_code", "urn:ietf:params:oauth:grant-type:pre-authorized_code", ], }; return NextResponse.json(issuerMetadata, { headers: { "Content-Type": "application/json", "Cache-Control": "no-cache, no-store, must-revalidate", Pragma: "no-cache", Expires: "0", }, }); }
2. OpenID कॉन्फ़िगरेशन (/.well-known/openid-configuration
)
यह एक मानक OIDC डिस्कवरी दस्तावेज़ है जो कॉन्फ़िगरेशन विवरणों का एक व्यापक सेट प्रदान करता है।
फ़ाइल src/app/.well-known/openid-configuration/route.ts
बनाएं:
// src/app/.well-known/openid-configuration/route.ts import { NextResponse } from "next/server"; export async function GET() { const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"; const openidConfiguration = { // The issuer's unique identifier. credential_issuer: baseUrl, // The endpoint where the wallet will POST to receive the actual credential. credential_endpoint: `${baseUrl}/api/issue/credential`, // The endpoint for the authorization flow. authorization_endpoint: `${baseUrl}/api/issue/authorize`, // The endpoint where the wallet exchanges an authorization code for an access token. token_endpoint: `${baseUrl}/api/issue/token`, // A list of the credential types this issuer can issue. credential_configurations_supported: { "eu.europa.ec.eudi.pid.1": { format: "jwt_vc", scope: "eu.europa.ec.eudi.pid.1", cryptographic_binding_methods_supported: ["jwk"], credential_signing_alg_values_supported: ["ES256", "ES384", "ES512"], proof_types_supported: { jwt: { proof_signing_alg_values_supported: ["ES256", "ES384", "ES512"], }, }, }, }, // OAuth 2.0 grant types the issuer supports. grant_types_supported: [ "authorization_code", "urn:ietf:params:oauth:grant-type:pre-authorized_code", ], // Indicates support for the pre-authorized code flow. pre_authorized_grant_anonymous_access_supported: true, // PKCE code challenge methods supported. code_challenge_methods_supported: ["S256"], // Authentication methods supported by the token endpoint. token_endpoint_auth_methods_supported: ["none"], // OAuth 2.0 scopes the issuer supports. scopes_supported: ["eu.europa.ec.eudi.pid.1"], }; return NextResponse.json(openidConfiguration, { headers: { "Content-Type": "application/json", "Cache-Control": "no-cache, no-store, must-revalidate", Pragma: "no-cache", Expires: "0", }, }); }
3. DID दस्तावेज़ (/.well-known/did.json
)
यह फ़ाइल did:web
विधि का उपयोग करके जारीकर्ता की सार्वजनिक कुंजी प्रकाशित करती है, जिससे
कोई भी इसके द्वारा जारी किए गए क्रेडेंशियल के हस्ताक्षर को सत्यापित कर सकता है।
फ़ाइल src/app/.well-known/did.json/route.ts
बनाएं:
// src/app/.well-known/did.json/route.ts import { NextResponse } from "next/server"; import { getActiveIssuerKey } from "../../../lib/database"; import { generateIssuerDid } from "../../../lib/crypto"; export async function GET() { const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"; const issuerKey = await getActiveIssuerKey(); if (!issuerKey) { return NextResponse.json( { error: "No active issuer key found" }, { status: 404 }, ); } const publicKeyJWK = JSON.parse(issuerKey.public_key); const didId = generateIssuerDid(); const didDocument = { // The context defines the vocabulary used in the document. "@context": [ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1", ], // The DID URI, which is the unique identifier for the issuer. id: didId, // The DID controller, which is the entity that controls the DID. Here, it's the issuer itself. controller: didId, // A list of public keys that can be used to verify signatures from the issuer. verificationMethod: [ { // A unique identifier for the key, scoped to the DID. id: `${didId}#${issuerKey.key_id}`, // The type of the key. type: "JsonWebKey2020", // The DID of the key's controller. controller: didId, // The public key in JWK format. publicKeyJwk: publicKeyJWK, }, ], // Specifies which keys can be used for authentication (proving control of the DID). authentication: [`${didId}#${issuerKey.key_id}`], // Specifies which keys can be used for creating verifiable credentials. assertionMethod: [`${didId}#${issuerKey.key_id}`], // A list of services provided by the DID subject, such as the issuer endpoint. service: [ { id: `${didId}#openid-credential-issuer`, type: "OpenIDCredentialIssuer", serviceEndpoint: `${baseUrl}/.well-known/openid-credential-issuer`, }, ], }; return NextResponse.json(didDocument, { headers: { "Content-Type": "application/did+json", "Cache-Control": "no-cache, no-store, must-revalidate", Pragma: "no-cache", Expires: "0", }, }); }
कैशिंग क्यों नहीं? आप देखेंगे कि ये तीनों एंडपॉइंट्स हेडर लौटाते हैं जो आक्रामक रूप
से कैशिंग को रोकते हैं (Cache-Control: no-cache
, Pragma: no-cache
, Expires: 0
)। यह
डिस्कवरी दस्तावेज़ों के लिए एक महत्वपूर्ण सुरक्षा अभ्यास है। जारीकर्ता कॉन्फ़िगरेशन बदल
सकते हैं-उदाहरण के लिए, एक क्रिप्टोग्राफ़िक कुंजी को घुमाया जा सकता है। यदि कोई वॉलेट या
क्लाइंट did.json
या openid-credential-issuer
फ़ाइल के पुराने संस्करण को कैश करता है,
तो यह नए क्रेडेंशियल को मान्य करने या अपडेट किए गए एंडपॉइंट्स के साथ इंटरैक्ट करने में
विफल हो जाएगा। क्लाइंट को प्रत्येक अनुरोध पर एक ताज़ा प्रतिलिपि प्राप्त करने के लिए
मजबूर करके, हम यह सुनिश्चित करते हैं कि उनके पास हमेशा सबसे अद्यतित जानकारी हो।
हमारे सार्वजनिक-सामना करने वाले बुनियादी ढांचे का अंतिम टुकड़ा क्रेडेंशियल स्कीमा एंडपॉइंट है। यह रूट एक JSON स्कीमा परोसता है जो औपचारिक रूप से PID क्रेडेंशियल की संरचना, डेटा प्रकारों और बाधाओं को परिभाषित करता है जिसे हम जारी कर रहे हैं। वॉलेट और सत्यापनकर्ता इस स्कीमा का उपयोग क्रेडेंशियल की सामग्री को मान्य करने के लिए कर सकते हैं।
निम्नलिखित सामग्री के साथ src/app/api/schemas/pid/route.ts
फ़ाइल बनाएं:
// src/app/api/schemas/pid/route.ts import { NextResponse } from "next/server"; export async function GET() { const schema = { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "https://example.com/schemas/pid", // Replace with your actual domain title: "PID Credential", description: "A schema for a Verifiable Credential representing a Personal Identification Document (PID).", type: "object", properties: { credentialSubject: { type: "object", properties: { given_name: { type: "string" }, family_name: { type: "string" }, birth_date: { type: "string", format: "date" }, // ... other properties of the credential subject }, required: ["given_name", "family_name", "birth_date"], }, // ... other top-level properties of a Verifiable Credential }, }; return NextResponse.json(schema, { headers: { "Content-Type": "application/schema+json", "Access-Control-Allow-Origin": "*", // Allow cross-origin requests }, }); }
नोट: एक PID क्रेडेंशियल के लिए JSON स्कीमा काफी बड़ा और विस्तृत हो सकता है। संक्षिप्तता के लिए, पूर्ण स्कीमा को छोटा कर दिया गया है। आप पूरी फ़ाइल प्रोजेक्ट रिपॉजिटरी में पा सकते हैं।
फ्रंटएंड के साथ, अब हमें OpenID4VCI प्रवाह को संभालने के लिए सर्वर-साइड लॉजिक की आवश्यकता
है। हम पहले एंडपॉइंट से शुरू करेंगे जिसे फ्रंटएंड कॉल करता है: /api/issue/authorize
।
/api/issue/authorize
: क्रेडेंशियल ऑफ़र बनाएँ#यह एंडपॉइंट यूजर के डेटा को लेने, एक सुरक्षित वन-टाइम-यूज़ कोड जेनरेट करने, और एक
credential_offer
बनाने के लिए ज़िम्मेदार है जिसे यूजर का वॉलेट समझ सकता है।
यहाँ कोर लॉजिक है:
// src/app/api/issue/authorize/route.ts import { NextRequest, NextResponse } from "next/server"; import { v4 as uuidv4 } from "uuid"; import { createAuthorizationCode } from "@/lib/database"; export async function POST(request: NextRequest) { try { const body = await request.json(); const { user_data } = body; // 1. Validate user data if ( !user_data || !user_data.given_name || !user_data.family_name || !user_data.birth_date ) { return NextResponse.json({ error: "missing_user_data" }, { status: 400 }); } // 2. Generate a pre-authorized code and a PIN const code = uuidv4(); const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes const txCode = Math.floor(1000 + Math.random() * 9000).toString(); // 4-digit PIN // 3. Store the code and user data await createAuthorizationCode(uuidv4(), code, expiresAt); // Note: This uses an in-memory store for demo purposes only. // In production, persist data securely in a database with proper expiry. if (!(global as any).userDataStore) (global as any).userDataStore = new Map(); (global as any).userDataStore.set(code, user_data); if (!(global as any).txCodeStore) (global as any).txCodeStore = new Map(); (global as any).txCodeStore.set(code, txCode); // 4. Create the credential offer object const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"; const credentialOffer = { // The issuer's identifier, which is its base URL. credential_issuer: baseUrl, // An array of credential types the issuer is offering. credential_configuration_ids: ["eu.europa.ec.eudi.pid.1"], // Specifies the grant types the wallet can use. grants: { // We are using the pre-authorized code flow. "urn:ietf:params:oauth:grant-type:pre-authorized_code": { // The one-time code the wallet will exchange for a token. "pre-authorized_code": code, // Indicates that the user must enter a PIN (tx_code) to redeem the code. user_pin_required: true, }, }, }; // 5. Create the full credential offer URI (a deep link for wallets) const credentialOfferUri = `openid-credential-offer://?credential_offer=${encodeURIComponent( JSON.stringify(credentialOffer), )}`; // The final response to the frontend. return NextResponse.json({ // The deep link for the QR code. credential_offer_uri: credentialOfferUri, // The raw pre-authorized code, for display or manual entry. pre_authorized_code: code, // The 4-digit PIN the user must enter in their wallet. tx_code: txCode, }); } catch (error) { console.error("Authorization error:", error); return NextResponse.json({ error: "server_error" }, { status: 500 }); } }
इस एंडपॉइंट में मुख्य चरण:
pre-authorized_code
(एक UUID) और सुरक्षा की एक
अतिरिक्त परत के लिए 4-अंकीय tx_code
(PIN) बनाता है।pre-authorized_code
को डेटाबेस में एक छोटी समाप्ति समय के साथ
संग्रहीत किया जाता है। यूजर का डेटा और पिन इन-मेमोरी में संग्रहीत होते हैं, जो कोड से
जुड़े होते हैं।credential_offer
ऑब्जेक्ट का
निर्माण करता है। यह ऑब्जेक्ट वॉलेट को बताता है कि जारीकर्ता कहाँ है, वह कौन से
क्रेडेंशियल प्रदान करता है, और उन्हें प्राप्त करने के लिए आवश्यक कोड क्या है।openid-credential-offer://...
) बनाता है
और इसे फ्रंटएंड को लौटाता है, साथ ही यूजर को देखने के लिए tx_code
भी।/api/issue/token
: कोड को टोकन के लिए एक्सचेंज करें#एक बार जब यूजर QR कोड को स्कैन करता है और अपना पिन दर्ज करता है, तो वॉलेट इस एंडपॉइंट पर
एक POST
अनुरोध करता है। इसका काम pre-authorized_code
और user_pin
(PIN) को मान्य करना
है, और यदि वे मान्य हैं, तो एक अल्पकालिक एक्सेस टोकन जारी करना
है।
निम्नलिखित सामग्री के साथ src/app/api/issue/token/route.ts
फ़ाइल बनाएं:
// src/app/api/issue/token/route.ts import { NextRequest, NextResponse } from "next/server"; import { v4 as uuidv4 } from "uuid"; import { getAuthorizationCode, markAuthorizationCodeAsUsed, createIssuanceSession, } from "@/lib/database"; export async function POST(request: NextRequest) { try { const formData = await request.formData(); const grant_type = formData.get("grant_type") as string; const code = formData.get("pre-authorized_code") as string; const user_pin = formData.get("user_pin") as string; // 1. Validate the grant type if (grant_type !== "urn:ietf:params:oauth:grant-type:pre-authorized_code") { return NextResponse.json( { error: "unsupported_grant_type" }, { status: 400 }, ); } // 2. Validate the pre-authorized code const authCode = await getAuthorizationCode(code); if (!authCode) { return NextResponse.json( { error: "invalid_grant", error_description: "Invalid or expired code", }, { status: 400 }, ); } // 3. Validate the PIN (tx_code) const expectedTxCode = (global as any).txCodeStore?.get(code); if (expectedTxCode !== user_pin) { return NextResponse.json( { error: "invalid_grant", error_description: "Invalid PIN" }, { status: 400 }, ); } // 4. Generate access token and c_nonce const accessToken = uuidv4(); const cNonce = uuidv4(); const cNonceExpiresAt = new Date(Date.now() + 5 * 60 * 1000); // 5 minutes // 5. Create a new issuance session const userData = (global as any).userDataStore?.get(code); await createIssuanceSession( uuidv4(), authCode.id, accessToken, cNonce, cNonceExpiresAt, userData, ); // 6. Mark the code as used and clean up temporary data await markAuthorizationCodeAsUsed(code); (global as any).txCodeStore?.delete(code); (global as any).userDataStore?.delete(code); // 7. Return the access token response return NextResponse.json({ access_token: accessToken, token_type: "Bearer", expires_in: 3600, // 1 hour c_nonce: cNonce, c_nonce_expires_in: 300, // 5 minutes }); } catch (error) { console.error("Token endpoint error:", error); return NextResponse.json({ error: "server_error" }, { status: 500 }); } }
इस एंडपॉइंट में मुख्य चरण:
pre-authorized_code
अनुदान प्रकार का उपयोग कर रहा है।pre-authorized_code
डेटाबेस में मौजूद है, समाप्त
नहीं हुआ है, और पहले उपयोग नहीं किया गया है।user_pin
की तुलना हमारे द्वारा पहले संग्रहीत
tx_code
से करता है ताकि यह सुनिश्चित हो सके कि यूजर ने लेनदेन को अधिकृत किया है।access_token
और एक c_nonce
(क्रेडेंशियल नॉन्स)
बनाता है, जो क्रेडेंशियल एंडपॉइंट पर रीप्ले हमलों को रोकने के लिए एक वन-टाइम-यूज़ मान
है।issuance_sessions
रिकॉर्ड बनाता है, जो
एक्सेस टोकन को यूजर के डेटा से जोड़ता है।pre-authorized_code
को प्रयुक्त के रूप में चिह्नित करता है।access_token
और c_nonce
को वॉलेट को लौटाता है।/api/issue/credential
: हस्ताक्षरित क्रेडेंशियल जारी करें#यह अंतिम और सबसे महत्वपूर्ण एंडपॉइंट है। वॉलेट /token
एंडपॉइंट से प्राप्त एक्सेस टोकन का
उपयोग इस रूट पर एक प्रमाणित POST
अनुरोध करने के लिए करता है। इस एंडपॉइंट का काम अंतिम
सत्यापन करना, क्रिप्टोग्राफ़िक रूप से हस्ताक्षरित क्रेडेंशियल बनाना, और इसे वॉलेट को
लौटाना है।
निम्नलिखित सामग्री के साथ src/app/api/issue/credential/route.ts
फ़ाइल बनाएं:
// src/app/api/issue/credential/route.ts import { NextRequest, NextResponse } from "next/server"; import { v4 as uuidv4 } from "uuid"; import { getIssuanceSessionByToken, updateIssuanceSession, createIssuedCredential, getActiveIssuerKey, } from "@/lib/database"; import { createJWTVerifiableCredential, importIssuerKeyPair, generateIssuerDid, } from "@/lib/crypto"; export async function POST(request: NextRequest) { try { // 1. Validate the Bearer token const authHeader = request.headers.get("authorization"); const accessToken = authHeader?.substring(7); const session = await getIssuanceSessionByToken(accessToken); if (!session) { return NextResponse.json({ error: "invalid_token" }, { status: 401 }); } // 2. Get the user data from the session const userData = session.user_data; if (!userData) { return NextResponse.json({ error: "missing_user_data" }, { status: 400 }); } // 3. Get the active issuer key const issuerKey = await getActiveIssuerKey(); if (!issuerKey) { // In a real application, you would have a more robust key management system. // For this demo, we can generate a key on the fly if one doesn't exist. // This part is omitted for brevity but is in the repository. return NextResponse.json( { error: "server_error", error_description: "Failed to get issuer key", }, { status: 500 }, ); } // 4. Create the JWT-VC const issuerDid = generateIssuerDid(); const keyPair = await importIssuerKeyPair( issuerKey.key_id, issuerKey.public_key, issuerKey.private_key, issuerDid, ); const subjectId = `did:example:${uuidv4()}`; const credentialData = await createJWTVerifiableCredential( userData, keyPair, subjectId, process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000", ); // 5. Store the issued credential in the database await createIssuedCredential(/* ... credential details ... */); await updateIssuanceSession(session.id, "credential_issued"); // 6. Return the signed credential return NextResponse.json({ format: "jwt_vc", credential: credentialData, c_nonce: uuidv4(), // A new nonce for subsequent requests c_nonce_expires_in: 300, }); } catch (error) { console.error("Credential endpoint error:", error); return NextResponse.json({ error: "server_error" }, { status: 500 }); } }
इस एंडपॉइंट में मुख्य चरण:
Authorization
हेडर में एक मान्य Bearer
टोकन की जांच करता है
और इसका उपयोग सक्रिय जारी करने के सत्र को देखने के लिए करता है।src/lib/crypto.ts
से हमारे createJWTVerifiableCredential
हेल्पर को कॉल करता है।अब आपके पास एक डिजिटल क्रेडेंशियल जारीकर्ता का एक पूर्ण, एंड-टू-एंड कार्यान्वयन है। यहाँ इसे स्थानीय रूप से कैसे चलाना है और इसे एक प्रूफ-ऑफ-कॉन्सेप्ट से उत्पादन-तैयार एप्लिकेशन तक ले जाने के लिए आपको क्या विचार करने की आवश्यकता है।
रिपॉजिटरी को क्लोन करें:
git clone https://github.com/corbado/digital-credentials-example.git cd digital-credentials-example
निर्भरताएँ स्थापित करें:
npm install
डेटाबेस शुरू करें: सुनिश्चित करें कि Docker चल रहा है, फिर MySQL कंटेनर शुरू करें:
docker-compose up -d
पर्यावरण कॉन्फ़िगर करें और टनल चलाएं: यह स्थानीय परीक्षण के लिए सबसे महत्वपूर्ण कदम
है। चूंकि आपके मोबाइल वॉलेट को इंटरनेट पर आपके विकास मशीन से कनेक्ट करने की आवश्यकता
है, इसलिए आपको अपने स्थानीय सर्वर को एक सार्वजनिक HTTPS URL के साथ उजागर करना होगा। हम
इसके लिए ngrok
का उपयोग करेंगे।
a. ngrok शुरू करें:
ngrok http 3000
b. ngrok आउटपुट से HTTPS URL को
कॉपी करें (जैसे, https://random-string.ngrok.io
)। c. एक .env.local
फ़ाइल
बनाएं और URL सेट करें:
NEXT_PUBLIC_BASE_URL=https://<your-ngrok-url>
एप्लिकेशन चलाएं:
npm run dev
अपने ब्राउज़र को http://localhost:3000/issue
पर खोलें। अब आप फ़ॉर्म भर सकते हैं, और
जेनरेट किया गया QR कोड सही ढंग से आपके सार्वजनिक ngrok URL को इंगित करेगा, जिससे आपका
मोबाइल वॉलेट कनेक्ट हो सकेगा और क्रेडेंशियल प्राप्त कर सकेगा।
ngrok
का महत्व#डिजिटल क्रेडेंशियल प्रोटोकॉल सुरक्षा को सर्वोच्च प्राथमिकता के रूप में बनाया गया है। इस
कारण से, वॉलेट लगभग हमेशा एक असुरक्षित (http://
) कनेक्शन पर एक जारीकर्ता से कनेक्ट करने
से इनकार कर देंगे। पूरी प्रक्रिया एक सुरक्षित HTTPS कनेक्शन पर निर्भर करती है, जो एक
SSL प्रमाणपत्र द्वारा सक्षम है।
ngrok
जैसी एक टनल सेवा दोनों समस्याओं को हल करती है, एक सुरक्षित, सार्वजनिक-सामना करने
वाला HTTPS URL (एक मान्य SSL प्रमाणपत्र के साथ) बनाकर जो सभी ट्रैफ़िक को आपके स्थानीय
विकास सर्वर पर अग्रेषित करता है। वॉलेट को HTTPS की आवश्यकता होती है और वे असुरक्षित
(http://
) एंडपॉइंट्स से कनेक्ट करने से इनकार कर देंगे। यह किसी भी वेब सेवा का परीक्षण
करने के लिए एक आवश्यक उपकरण है जिसे मोबाइल उपकरणों या बाहरी वेबहुक के साथ इंटरैक्ट करने की
आवश्यकता होती है।
यह उदाहरण जानबूझकर कोर जारी करने के प्रवाह पर केंद्रित है ताकि इसे समझना आसान हो सके। निम्नलिखित विषय दायरे से बाहर माने जाते हैं:
revoked
ध्वज शामिल है,
यहाँ कोई निरस्तीकरण तर्क प्रदान नहीं किया गया है।pre-authorized_code
फ्लो पर ध्यान केंद्रित
किया। authorization_code
फ्लो का एक पूर्ण कार्यान्वयन एक यूजर सहमति स्क्रीन और अधिक
जटिल OAuth 2.0 तर्क की आवश्यकता होगी।बस इतना ही! कोड के कुछ पन्नों के साथ, अब हमारे पास एक पूर्ण, एंड-टू-एंड डिजिटल क्रेडेंशियल जारीकर्ता है जो:
pre-authorized_code
फ्लो को लागू करता है।जबकि यह गाइड एक ठोस आधार प्रदान करता है, एक उत्पादन-तैयार जारीकर्ता को मजबूत कुंजी प्रबंधन, इन-मेमोरी स्टोर के बजाय स्थायी भंडारण, क्रेडेंशियल निरस्तीकरण, और व्यापक सुरक्षा सुदृढीकरण जैसी अतिरिक्त सुविधाओं की आवश्यकता होगी। वॉलेट संगतता भी भिन्न होती है; परीक्षण के लिए Sphereon Wallet की सिफारिश की जाती है, लेकिन अन्य वॉलेट यहां लागू किए गए पूर्व-अधिकृत प्रवाह का समर्थन नहीं कर सकते हैं। हालांकि, कोर बिल्डिंग ब्लॉक्स और इंटरैक्शन फ्लो वही रहेंगे। इन पैटर्न का पालन करके, आप किसी भी प्रकार के डिजिटल क्रेडेंशियल के लिए एक सुरक्षित और इंटरऑपरेबल जारीकर्ता बना सकते हैं।
यहाँ कुछ प्रमुख संसाधन, विनिर्देश और उपकरण दिए गए हैं जिनका इस ट्यूटोरियल में उपयोग या संदर्भ दिया गया है:
प्रोजेक्ट रिपॉजिटरी:
प्रमुख विनिर्देश:
did:web
Method: हमारे जारीकर्ता
की सार्वजनिक कुंजी के लिए उपयोग की जाने वाली DID विधि।उपकरण:
लाइब्रेरीज़:
Related Articles
Table of Contents