Get your free and exclusive 80-page Banking Passkey Report
passkey tutorial how to implement passkeys

पासकी ट्यूटोरियल: वेब ऐप्स में पासकी कैसे लागू करें

यह ट्यूटोरियल बताता है कि आप अपने वेब ऐप में पासकी कैसे लागू कर सकते हैं। हम Node.js (TypeScript), SimpleWebAuthn, Vanilla HTML / JavaScript और MySQL का उपयोग करते हैं।

Vincent Delitz

Vincent

Created: June 17, 2025

Updated: June 20, 2025


We aim to make the Internet a safer place using passkeys. That's why we want to support developers with tutorials on how to implement passkeys.

1. परिचय: पासकी कैसे लागू करें#

इस ट्यूटोरियल में, हम आपके पासकी कार्यान्वयन प्रयासों में आपकी मदद करते हैं, और आपकी वेबसाइट पर पासकी जोड़ने के तरीके पर एक चरण-दर-चरण मार्गदर्शिका प्रदान करते हैं।

Demo Icon

Want to try passkeys yourself in a passkeys demo?

Try Passkeys

जब आप एक बेहतरीन वेबसाइट या ऐप बनाना चाहते हैं तो एक आधुनिक, मजबूत और उपयोगकर्ता-अनुकूल प्रमाणीकरण होना महत्वपूर्ण है। पासकी इस चुनौती के उत्तर के रूप में उभरी हैं। लॉगिन के लिए नए मानक के रूप में काम करते हुए, वे पारंपरिक पासवर्ड के नुकसान के बिना भविष्य का वादा करते हैं, एक वास्तविक पासवर्ड रहित लॉगिन अनुभव प्रदान करते हैं (जो न केवल सुरक्षित है बल्कि अत्यधिक सुविधाजनक भी है)।

जो चीज वास्तव में पासकी की क्षमता को व्यक्त करती है, वह है उन्हें मिला समर्थन। हर महत्वपूर्ण ब्राउज़र चाहे वह क्रोम, फ़ायरफ़ॉक्स, सफारी, या एज हो और सभी महत्वपूर्ण डिवाइस निर्माता (एप्पल, माइक्रोसॉफ्ट, गूगल) ने समर्थन शामिल किया है। यह सर्वसम्मत आलिंगन दर्शाता है कि पासकी लॉगिन के लिए नया मानक है।

हाँ, वेब अनुप्रयोगों में पासकी को एकीकृत करने पर पहले से ही ट्यूटोरियल मौजूद हैं। चाहे वह React, Vue.js या Next.js जैसे फ्रंटएंड फ्रेमवर्क के लिए हो, चुनौतियों को कम करने और आपके पासकी कार्यान्वयन को गति देने के लिए डिज़ाइन की गई गाइडों की एक बड़ी संख्या है। हालाँकि, एक एंड-2-एंड ट्यूटोरियल जो न्यूनतम और बेयर-मेटल बना रहता है, उसकी कमी है। कई डेवलपर्स ने हमसे संपर्क किया है और वेब ऐप्स के लिए पासकी कार्यान्वयन में स्पष्टता लाने वाले ट्यूटोरियल के लिए कहा है।

यही कारण है कि हमने यह गाइड तैयार की है। हमारा उद्देश्य? पासकी के लिए एक न्यूनतम व्यवहार्य सेटअप बनाना, जिसमें फ्रंटएंड, बैकएंड और डेटाबेस लेयर शामिल हो (बाद वाला अक्सर उपेक्षित होता है, भले ही यह कुछ गंभीर सिरदर्द पैदा कर सकता है)।

PasskeyAssessment Icon

Get a free passkey assessment in 15 minutes.

Book free consultation

इस यात्रा के अंत तक, आपने एक न्यूनतम व्यवहार्य वेब एप्लिकेशन बना लिया होगा, जहाँ आप कर सकते हैं:

  • एक पासकी बनाएँ
  • लॉगिन करने के लिए पासकी का उपयोग करें

जो लोग जल्दी में हैं या एक संदर्भ चाहते हैं, उनके लिए पूरा कोडबेस GitHub पर उपलब्ध है।

जिज्ञासु हैं कि अंतिम परिणाम कैसा दिखता है? यहाँ अंतिम प्रोजेक्ट की एक झलक है (हम मानते हैं कि यह बहुत ही बुनियादी दिखता है लेकिन दिलचस्प चीजें सतह के नीचे हैं):

हम पूरी तरह से जानते हैं कि कोड और प्रोजेक्ट के कुछ हिस्सों को अलग-अलग या अधिक परिष्कृत तरीके से किया जा सकता है लेकिन हम अनिवार्य चीजों पर ध्यान केंद्रित करना चाहते थे। इसीलिए हमने जानबूझकर चीजों को सरल और पासकी-केंद्रित रखा है।

StateOfPasskeys Icon

Want to find out how many people use passkeys?

View Adoption Data

मेरी प्रोडक्शन वेबसाइट पर पासकी कैसे जोड़ें?

यह पासकी प्रमाणीकरण के लिए एक बहुत ही न्यूनतम उदाहरण है। निम्नलिखित बातें इस ट्यूटोरियल में नहीं मानी गई हैं / लागू की गई हैं या केवल बहुत ही बुनियादी हैं:

  • कंडीशनल यूआई / कंडीशनल मीडिएशन / पासकी ऑटोफिल
  • डिवाइस प्रबंधन
  • सत्र प्रबंधन
  • एक खाते में सुरक्षित रूप से कई डिवाइस जोड़ना
  • बैकवर्ड संगतता
  • उचित क्रॉस-प्लेटफ़ॉर्म और क्रॉस-डिवाइस समर्थन
  • फ़ॉलबैक प्रमाणीकरण
  • उचित त्रुटि प्रबंधन
  • पासकी प्रबंधन पृष्ठ

इन सभी सुविधाओं के लिए पूर्ण समर्थन प्राप्त करने के लिए बहुत अधिक विकास प्रयास की आवश्यकता होती है। जो लोग रुचि रखते हैं, उनके लिए हम इस पासकी डेवलपर की गलतफहमी वाले लेख पर एक नज़र डालने की सलाह देते हैं।

Slack Icon

Become part of our Passkeys Community for updates & support.

Join

2. पासकी को एकीकृत करने के लिए पूर्वापेक्षाएँ#

पासकी कार्यान्वयन में गहराई से उतरने से पहले, आइए आवश्यक कौशल और उपकरणों पर एक नज़र डालें। यहाँ आपको आरंभ करने के लिए क्या चाहिए:

2.1 फ्रंटएंड: वैनिला HTML और जावास्क्रिप्ट#

वेब के बिल्डिंग ब्लॉक्स HTML, CSS, और जावास्क्रिप्ट की एक ठोस समझ आवश्यक है। हमने जानबूझकर चीजों को सीधा रखा है, किसी भी आधुनिक जावास्क्रिप्ट फ्रेमवर्क से परहेज किया है और वैनिला जावास्क्रिप्ट / HTML पर भरोसा किया है। एकमात्र अधिक परिष्कृत चीज जिसका हम उपयोग करते हैं वह है WebAuthn रैपर लाइब्रेरी @simplewebauthn/browser

2.2 बैकएंड: Node.js (Express) TypeScript में + SimpleWebAuthn#

हमारे बैकएंड के लिए, हम TypeScript में लिखे गए एक Node.js (Express) सर्वर का उपयोग करते हैं। हमने SimpleWebAuthn के WebAuthn सर्वर कार्यान्वयन (@simplewebauthn/server के साथ @simplewebauthn/typescript-types) के साथ काम करने का भी निर्णय लिया है। कई WebAuthn सर्वर कार्यान्वयन उपलब्ध हैं, इसलिए आप निश्चित रूप से इनमें से किसी का भी उपयोग कर सकते हैं। जैसा कि हमने TypeScript WebAuthn सर्वर के लिए निर्णय लिया है, बुनियादी Node.js और npm ज्ञान आवश्यक है।

2.3 डेटाबेस: MySQL#

सभी उपयोगकर्ता डेटा और पासकी की सार्वजनिक कुंजियाँ एक डेटाबेस में संग्रहीत की जाती हैं। हमने डेटाबेस तकनीक के रूप में MySQL को चुना है। MySQL और रिलेशनल डेटाबेस की एक मूलभूत समझ फायदेमंद है, हालांकि हम आपको एकल चरणों के माध्यम से मार्गदर्शन करेंगे।

निम्नलिखित में, हम अक्सर WebAuthn और पासकी शब्दों का परस्पर उपयोग करते हैं, भले ही वे आधिकारिक तौर पर एक ही चीज़ न हों। बेहतर समझ के लिए, विशेष रूप से कोड भाग में, हम यह धारणा बनाते हैं।

इन पूर्वापेक्षाओं के साथ, आप पासकी की दुनिया में गोता लगाने के लिए पूरी तरह तैयार हैं।

Ben Gould Testimonial

Ben Gould

Head of Engineering

I’ve built hundreds of integrations in my time, including quite a few with identity providers and I’ve never been so impressed with a developer experience as I have been with Corbado.

10,000+ डेवलपर्स Corbado पर भरोसा करते हैं और पासकी के साथ इंटरनेट को सुरक्षित बनाते हैं। क्या आपके कोई प्रश्न हैं? हमने पासकी पर 150+ ब्लॉग पोस्ट लिखे हैं।

पासकी समुदाय में शामिल हों

3. आर्किटेक्चर अवलोकन: पासकी उदाहरण कार्यान्वयन#

कोड और कॉन्फ़िगरेशन में जाने से पहले, आइए उस सिस्टम के आर्किटेक्चर पर एक नज़र डालें जिसे हम बनाना चाहते हैं। यहाँ उस आर्किटेक्चर का एक विश्लेषण है जिसे हम स्थापित करेंगे:

  • फ्रंटएंड: इसमें दो बटन होते हैं - एक उपयोगकर्ता पंजीकरण (एक पासकी बनाना) के लिए और दूसरा प्रमाणीकरण (पासकी का उपयोग करके लॉगिन करना) के लिए।
  • डिवाइस और ब्राउज़र: एक बार जब फ्रंटएंड पर कोई क्रिया शुरू हो जाती है, तो डिवाइस और ब्राउज़र काम में आते हैं। वे पासकी के निर्माण और सत्यापन की सुविधा प्रदान करते हैं, जो उपयोगकर्ता और बैकएंड के बीच मध्यस्थ के रूप में कार्य करते हैं।
  • बैकएंड: बैकएंड वह जगह है जहाँ हमारे एप्लिकेशन में असली जादू होता है। यह फ्रंटएंड द्वारा शुरू किए गए सभी अनुरोधों को संभालता है। इस प्रक्रिया में पासकी बनाना और सत्यापित करना शामिल है। बैकएंड संचालन के मूल में WebAuthn सर्वर है। नाम के विपरीत, यह एक स्टैंडअलोन सर्वर नहीं है। इसके बजाय, यह एक लाइब्रेरी या पैकेज है जो WebAuthn मानक को लागू करता है। दो प्राथमिक कार्य हैं: पंजीकरण (साइन-अप) जहाँ नए उपयोगकर्ता अपनी पासकी बनाते हैं और प्रमाणीकरण (लॉगिन): जहाँ मौजूदा उपयोगकर्ता अपनी पासकी का उपयोग करके लॉग इन करते हैं। अपने सरलतम रूप में, WebAuthn सर्वर चार सार्वजनिक API समापन बिंदु प्रदान करता है, जिन्हें दो श्रेणियों में विभाजित किया गया है: दो पंजीकरण के लिए और दो प्रमाणीकरण के लिए। वे एक विशिष्ट प्रारूप में डेटा प्राप्त करने के लिए डिज़ाइन किए गए हैं, जिसे बाद में WebAuthn सर्वर द्वारा संसाधित किया जाता है। WebAuthn सर्वर सभी आवश्यक क्रिप्टोग्राफ़िक संचालन के लिए ज़िम्मेदार है। ध्यान देने योग्य एक आवश्यक पहलू यह है कि इन API समापन बिंदुओं को HTTPS पर परोसा जाना चाहिए।
  • MySQL डेटाबेस: हमारे स्टोरेज बैकबोन के रूप में कार्य करते हुए, MySQL डेटाबेस उपयोगकर्ता डेटा और उनके संबंधित क्रेडेंशियल्स को रखने के लिए ज़िम्मेदार है।
Analyzer Icon

Are your users passkey-ready?

Test Passkey-Readiness

इस वास्तुशिल्प अवलोकन के साथ, आपके पास हमारे एप्लिकेशन के घटकों का एक वैचारिक नक्शा होना चाहिए। जैसे-जैसे हम आगे बढ़ेंगे, हम इनमें से प्रत्येक घटक में गहराई से उतरेंगे, उनके सेटअप, कॉन्फ़िगरेशन और परस्पर क्रिया का विवरण देंगे।

निम्नलिखित चार्ट रजिस्टर (साइन-अप) के दौरान प्रक्रिया प्रवाह का वर्णन करता है:

निम्नलिखित चार्ट प्रमाणीकरण (लॉगिन) के दौरान प्रक्रिया प्रवाह का वर्णन करता है:

इसके अलावा, आप यहाँ प्रोजेक्ट संरचना पाते हैं (केवल सबसे महत्वपूर्ण फाइलें):

passkeys-tutorial ├── src # सभी बैकएंड TypeScript स्रोत कोड शामिल हैं │ ├── controllers # विशिष्ट प्रकार के अनुरोधों को संभालने के लिए व्यावसायिक तर्क │ │ ├── authentication.ts # पासकी प्रमाणीकरण तर्क │ │ └── registration.ts # पासकी पंजीकरण तर्क │ ├── middleware │ │ ├── customError.ts # मानकीकृत तरीके से कस्टम त्रुटि संदेश जोड़ें │ │ └── errorHandler.ts # सामान्य त्रुटि हैंडलर │ ├── public │ │ ├── index.html # फ्रंटएंड में मुख्य HTML फ़ाइल │ │ ├── css │ │ │ └── style.css # बुनियादी स्टाइलिंग │ │ └── js │ │ └── script.js # जावास्क्रिप्ट तर्क (WebAuthn API सहित) │ ├── routes # API मार्गों और उनके हैंडलर्स की परिभाषाएँ │ │ └── routes.ts # विशिष्ट पासकी मार्ग │ ├── services │ │ ├── credentialService.ts# क्रेडेंशियल तालिका के साथ इंटरैक्ट करता है │ │ └── userService.ts # उपयोगकर्ता तालिका के साथ इंटरैक्ट करता है │ ├── utils # सहायक कार्य और उपयोगिताएँ │ | ├── constants.ts # कुछ स्थिरांक (जैसे rpID) │ | └── utils.ts # सहायक कार्य │ ├── database.ts # Node.js से MySQL डेटाबेस तक कनेक्शन बनाता है │ ├── index.ts # Node.js सर्वर का एंट्रीपॉइंट │ └── server.ts # सभी सर्वर सेटिंग्स का प्रबंधन करता है ├── config.json # Node.js प्रोजेक्ट के लिए कुछ कॉन्फ़िगरेशन ├── docker-compose.yml # Docker कंटेनरों के लिए सेवाओं, नेटवर्क और वॉल्यूम को परिभाषित करता है ├── Dockerfile # प्रोजेक्ट की एक Docker छवि बनाता है ├── init-db.sql # हमारी MySQL डेटाबेस योजना को परिभाषित करता है ├── package.json # Node.js प्रोजेक्ट निर्भरता और स्क्रिप्ट का प्रबंधन करता है └── tsconfig.json # कॉन्फ़िगर करता है कि TypeScript आपके कोड को कैसे संकलित करता है

4. MySQL डेटाबेस का सेटअप#

पासकी लागू करते समय, डेटाबेस सेटअप एक प्रमुख घटक है। हमारा दृष्टिकोण MySQL चलाने वाले एक Docker कंटेनर का उपयोग करता है, जो विश्वसनीय परीक्षण और परिनियोजन के लिए आवश्यक एक सीधा और पृथक वातावरण प्रदान करता है।

हमारी डेटाबेस योजना जानबूझकर न्यूनतम है, जिसमें केवल दो तालिकाएँ हैं। यह सादगी एक स्पष्ट समझ और आसान रखरखाव में सहायता करती है।

विस्तृत तालिका संरचना

1. क्रेडेंशियल तालिका: पासकी प्रमाणीकरण के लिए केंद्रीय, यह तालिका पासकी क्रेडेंशियल्स को संग्रहीत करती है। महत्वपूर्ण कॉलम:

  • credential_id: प्रत्येक क्रेडेंशियल के लिए एक अद्वितीय पहचानकर्ता। स्वरूपण त्रुटियों से बचने के लिए इस फ़ील्ड के लिए सही डेटा प्रकार का चयन करना महत्वपूर्ण है।
  • public_key: प्रत्येक क्रेडेंशियल के लिए सार्वजनिक कुंजी संग्रहीत करता है। credential_id की तरह, उपयुक्त डेटा प्रकार और स्वरूपण महत्वपूर्ण हैं।

2. उपयोगकर्ता तालिका: उपयोगकर्ता खातों को उनके संबंधित क्रेडेंशियल्स से जोड़ती है।

ध्यान दें कि हमने पहली तालिका का नाम क्रेडेंशियल रखा है क्योंकि यह हमारे अनुभव के अनुसार है और अन्य पुस्तकालयों की सिफारिश के अनुसार अधिक उपयुक्त है (SimpleWebAuthn के सुझाव के विपरीत इसे ऑथेंटिकेटर या authenticator_device नाम देना)।

credential_id और public_key के लिए डेटा प्रकार महत्वपूर्ण हैं। त्रुटियाँ अक्सर गलत डेटा प्रकार, एन्कोडिंग या स्वरूपण से उत्पन्न होती हैं (विशेष रूप से Base64 और Base64URL के बीच का अंतर त्रुटियों का एक सामान्य कारण है), जो पूरी पंजीकरण (साइन-अप) या प्रमाणीकरण (लॉगिन) प्रक्रिया को बाधित कर सकता है।

इन तालिकाओं को स्थापित करने के लिए सभी आवश्यक SQL कमांड init-db.sql फ़ाइल के भीतर निहित हैं। यह स्क्रिप्ट एक त्वरित और त्रुटि-मुक्त डेटाबेस आरंभीकरण सुनिश्चित करती है।

अधिक परिष्कृत मामलों के लिए, आप क्रेडेंशियल्स के बारे में अधिक जानकारी संग्रहीत करने और उपयोगकर्ता अनुभव को बेहतर बनाने के लिए credential_device_type या credential_backed_up जोड़ सकते हैं। हालाँकि, हम इस ट्यूटोरियल में इससे बचते हैं।

init-db.sql
CREATE TABLE users ( id VARCHAR(255) PRIMARY KEY, username VARCHAR(255) NOT NULL UNIQUE ); CREATE TABLE credentials ( id INT AUTO_INCREMENT PRIMARY KEY, user_id VARCHAR(255) NOT NULL, credential_id VARCHAR(255) NOT NULL, public_key TEXT NOT NULL, counter INT NOT NULL, transports VARCHAR(255), FOREIGN KEY (user_id) REFERENCES users (id) );

इस फ़ाइल को बनाने के बाद, हम प्रोजेक्ट के रूट स्तर पर एक नई docker-compose.yml फ़ाइल बनाते हैं:

docker-compose.yml
version: "3.1" services: db: image: mysql command: --default-authentication-plugin=mysql_native_password restart: always environment: MYSQL_ROOT_PASSWORD: my-secret-pw MYSQL_DATABASE: webauthn_db ports: - "3306:3306" volumes: - ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql

यह फ़ाइल पोर्ट 3306 पर MySQL डेटाबेस शुरू करती है और परिभाषित डेटाबेस संरचना बनाती है। यह ध्यान रखना महत्वपूर्ण है कि यहाँ उपयोग किए गए डेटाबेस के लिए नाम और पासवर्ड प्रदर्शन उद्देश्यों के लिए सरल रखे गए हैं। एक उत्पादन वातावरण में, आपको बढ़ी हुई सुरक्षा के लिए अधिक जटिल क्रेडेंशियल्स का उपयोग करना चाहिए।

इसके बाद, हम अपने Docker कंटेनर को चलाने के लिए आगे बढ़ते हैं। इस बिंदु पर, हमारी docker-compose.yml फ़ाइल में केवल यह एकल कंटेनर शामिल है, लेकिन हम बाद में और घटक जोड़ेंगे। कंटेनर शुरू करने के लिए, निम्नलिखित कमांड का उपयोग करें:

docker compose up -d

एक बार जब कंटेनर चालू हो जाता है, तो हमें यह सत्यापित करने की आवश्यकता है कि क्या डेटाबेस अपेक्षा के अनुरूप काम कर रहा है। एक टर्मिनल खोलें और MySQL डेटाबेस के साथ इंटरैक्ट करने के लिए निम्नलिखित कमांड निष्पादित करें:

docker exec -it <container ID> mysql -uroot -p

आपको रूट पासवर्ड दर्ज करने के लिए कहा जाएगा, जो हमारे उदाहरण में my-secret-pw है। लॉग इन करने के बाद, webauthn_db डेटाबेस का चयन करें और इन कमांड का उपयोग करके तालिकाओं को प्रदर्शित करें:

use webauthn_db; show tables;

इस स्तर पर, आपको हमारी स्क्रिप्ट में परिभाषित दो तालिकाएँ देखनी चाहिए। प्रारंभ में, ये तालिकाएँ खाली होंगी, यह दर्शाता है कि हमारा डेटाबेस सेटअप पूरा हो गया है और पासकी लागू करने के अगले चरणों के लिए तैयार है।

5. पासकी लागू करना: बैकएंड एकीकरण के चरण#

बैकएंड किसी भी पासकी एप्लिकेशन का मूल है, जो फ्रंटएंड से उपयोगकर्ता प्रमाणीकरण अनुरोधों को संसाधित करने के लिए केंद्रीय केंद्र के रूप में कार्य करता है। यह पंजीकरण (साइन-अप) और प्रमाणीकरण (लॉगिन) अनुरोधों को संभालने के लिए WebAuthn सर्वर लाइब्रेरी के साथ संचार करता है, और यह उपयोगकर्ता क्रेडेंशियल्स को संग्रहीत और पुनर्प्राप्त करने के लिए आपके MySQL डेटाबेस के साथ इंटरैक्ट करता है। नीचे, हम आपको TypeScript के साथ Node.js (Express) का उपयोग करके अपना बैकएंड स्थापित करने के लिए मार्गदर्शन करेंगे जो सभी अनुरोधों को संभालने के लिए एक सार्वजनिक API को उजागर करेगा।

5.1 Node.js (Express) सर्वर को इनिशियलाइज़ करें#

सबसे पहले, अपने प्रोजेक्ट के लिए एक नई डायरेक्टरी बनाएं और अपने टर्मिनल या कमांड प्रॉम्प्ट का उपयोग करके उसमें नेविगेट करें।

कमांड चलाएँ

npx create-express-typescript-application passkeys-tutorial

यह TypeScript में लिखे गए एक Node.js (Express) ऐप का एक बुनियादी कोड स्केलेटन बनाता है जिसे हम आगे के अनुकूलन के लिए उपयोग कर सकते हैं।

आपके प्रोजेक्ट को कई प्रमुख पैकेजों की आवश्यकता है जिन्हें हमें शीर्ष पर स्थापित करने की आवश्यकता है:

  • @simplewebauthn/server: WebAuthn संचालन, जैसे उपयोगकर्ता पंजीकरण (साइन-अप) और प्रमाणीकरण (लॉगिन) की सुविधा के लिए एक सर्वर-साइड लाइब्रेरी।
  • express-session: Express.js के लिए मिडलवेयर सत्रों का प्रबंधन करने, सर्वर-साइड सत्र डेटा संग्रहीत करने और कुकीज़ को संभालने के लिए।
  • uuid: सार्वभौमिक रूप से अद्वितीय पहचानकर्ता (UUID) उत्पन्न करने के लिए एक उपयोगिता, जिसका उपयोग आमतौर पर अनुप्रयोगों में अद्वितीय कुंजी या पहचानकर्ता बनाने के लिए किया जाता है।
  • mysql2: MySQL के लिए एक Node.js क्लाइंट, जो MySQL डेटाबेस से कनेक्ट करने और क्वेरी निष्पादित करने की क्षमता प्रदान करता है।

नई डायरेक्टरी में स्विच करें और उन्हें निम्नलिखित कमांड के साथ स्थापित करें (हम आवश्यक TypeScript प्रकार भी स्थापित करते हैं):

cd passkeys-tutorial npm install @simplewebauthn/server mysql2 uuid express-session @types/express-session @types/uuid

यह पुष्टि करने के लिए कि सब कुछ सही ढंग से स्थापित है, चलाएँ

npm run dev:nodemon

यह आपके Node.js सर्वर को Nodemon के साथ विकास मोड में शुरू करना चाहिए, जो किसी भी फ़ाइल परिवर्तन पर सर्वर को स्वचालित रूप से पुनरारंभ करता है।

समस्या निवारण टिप: यदि आपको त्रुटियाँ आती हैं, तो package.json फ़ाइल में ts-node को संस्करण 10.8.1 में अपडेट करने का प्रयास करें और फिर अपडेट इंस्टॉल करने के लिए npm i चलाएँ।

आपकी server.ts फ़ाइल में एक Express एप्लिकेशन के लिए बुनियादी सेटअप और मिडलवेयर है। पासकी कार्यक्षमता को एकीकृत करने के लिए, आपको जोड़ना होगा:

  • रूट्स: पासकी पंजीकरण (साइन-अप) और प्रमाणीकरण (लॉगिन) के लिए नए रूट्स परिभाषित करें।
  • कंट्रोलर्स: इन रूट्स के लिए तर्क को संभालने के लिए कंट्रोलर्स बनाएँ।
  • मिडलवेयर: अनुरोध और त्रुटि प्रबंधन के लिए मिडलवेयर को एकीकृत करें।
  • सेवाएँ: डेटाबेस में डेटा को पुनः प्राप्त करने और संग्रहीत करने के लिए सेवाएँ बनाएँ।
  • यूटिलिटी फ़ंक्शंस: कुशल कोड संचालन के लिए यूटिलिटी फ़ंक्शंस शामिल करें।

ये संवर्द्धन आपके एप्लिकेशन के बैकएंड में पासकी प्रमाणीकरण को सक्षम करने के लिए महत्वपूर्ण हैं। हम उन्हें बाद में स्थापित करते हैं।

Debugger Icon

Want to experiment with passkey flows? Try our Passkeys Debugger.

Try for Free

5.2 MySQL डेटाबेस कनेक्शन#

अनुभाग 4 में डेटाबेस बनाने और शुरू करने के बाद, अब हमें यह सुनिश्चित करने की आवश्यकता है कि हमारा बैकएंड MySQL डेटाबेस से कनेक्ट हो सकता है। इसलिए, हम /src फ़ोल्डर में एक नई database.ts फ़ाइल बनाते हैं और निम्नलिखित सामग्री जोड़ते हैं:

database.ts
import mysql from "mysql2"; // Create a MySQL pool const pool = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, waitForConnections: true, connectionLimit: 10, queueLimit: 0, }); // Promisify for Node.js async/await. export const promisePool = pool.promise();

यह फ़ाइल बाद में हमारे सर्वर द्वारा डेटाबेस तक पहुँचने के लिए उपयोग की जाएगी।

5.3 ऐप सर्वर कॉन्फ़िगरेशन#

आइए हमारे config.json पर एक संक्षिप्त नज़र डालें, जहाँ दो चर पहले से ही परिभाषित हैं: वह पोर्ट जहाँ हम एप्लिकेशन चलाते हैं और वातावरण:

config.json
{ "PORT": 8080, "NODE_ENV": "development" }

package.json जैसा है वैसा ही रह सकता है और ऐसा दिखना चाहिए:

package.json
{ "name": "passkeys-tutorial", "version": "0.0.1", "description": "passkeys-tutorial initialised with create-express-typescript-application.", "main": "src/index.ts", "scripts": { "build": "tsc", "start": "node ./build/src/index.js", "dev": "ts-node ./src/index.ts", "dev:nodemon": "nodemon -w src -e ts,json -x ts-node ./src/index.ts", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": ["express", "typescript"], "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/morgan": "^1.9.9", "@types/node": "^14.18.63", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", "eslint": "^7.32.0", "nodemon": "^2.0.22", "ts-node": "^10.8.1", "typescript": "^4.9.5" }, "dependencies": { "@simplewebauthn/server": "^8.3.5", "@types/express-session": "^1.17.10", "@types/uuid": "^9.0.7", "cors": "^2.8.5", "env-cmd": "^10.1.0", "express": "^4.18.2", "express-session": "^1.17.3", "fs": "^0.0.1-security", "helmet": "^4.6.0", "morgan": "^1.10.0", "mysql2": "^3.6.5", "uuid": "^9.0.1" } }

index.ts ऐसा दिखता है:

index.ts
import app from "./server"; import config from "../config.json"; // Start the application by listening to specific port const port = Number(process.env.PORT || config.PORT || 8080); app.listen(port, () => { console.info("Express application started on port: " + port); });

server.ts में, हमें कुछ और चीजों को अनुकूलित करने की आवश्यकता है। इसके अलावा, एक अस्थायी कैश की आवश्यकता होती है (जैसे redis, memcache या express-session) ताकि अस्थायी चुनौतियों को संग्रहीत किया जा सके जिनके खिलाफ उपयोगकर्ता प्रमाणित कर सकते हैं। हमने express-session का उपयोग करने का निर्णय लिया है और express-session के साथ काम करने के लिए शीर्ष पर express-session मॉड्यूल घोषित किया है। इसके अतिरिक्त, हम रूटिंग को सुव्यवस्थित करते हैं और अभी के लिए त्रुटि प्रबंधन को हटा देते हैं (यह बाद में मिडलवेयर में जोड़ा जाएगा):

server.ts
import express, { Express } from "express"; import morgan from "morgan"; import helmet from "helmet"; import cors from "cors"; import config from "../config.json"; import { router as passkeyRoutes } from "./routes/routes"; import session from "express-session"; const app: Express = express(); declare module "express-session" { interface SessionData { currentChallenge?: string; loggedInUserId?: string; } } /************************************************************************************ * Basic Express Middlewares ***********************************************************************************/ app.set("json spaces", 4); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use( session({ // @ts-ignore secret: process.env.SESSION_SECRET, saveUninitialized: true, resave: false, cookie: { maxAge: 86400000, httpOnly: true, // Ensure to not expose session cookies to clientside scripts }, }), ); // Handle logs in console during development if (process.env.NODE_ENV === "development" || config.NODE_ENV === "development") { app.use(morgan("dev")); app.use(cors()); } // Handle security and origin in production if (process.env.NODE_ENV === "production" || config.NODE_ENV === "production") { app.use(helmet()); } /************************************************************************************ * Register all routes ***********************************************************************************/ app.use("/api/passkey", passkeyRoutes); app.use(express.static("src/public")); export default app;

5.4 क्रेडेंशियल सर्विस और यूजर सर्विस#

हमारी दो बनाई गई तालिकाओं में डेटा को प्रभावी ढंग से प्रबंधित करने के लिए, हम एक नई src/services डायरेक्टरी में दो अलग-अलग सेवाएँ विकसित करेंगे: authenticatorService.ts और userService.ts

प्रत्येक सेवा CRUD (Create, Read, Update, Delete) विधियों को समाहित करेगी, जो हमें डेटाबेस के साथ एक मॉड्यूलर और संगठित तरीके से बातचीत करने में सक्षम बनाएगी। ये सेवाएँ ऑथेंटिकेटर और उपयोगकर्ता तालिकाओं में डेटा को संग्रहीत करने, पुनः प्राप्त करने और अद्यतन करने की सुविधा प्रदान करेंगी। यहाँ इन आवश्यक फ़ाइलों की संरचना कैसी होनी चाहिए:

userService.ts ऐसा दिखता है:

userService.ts
import { promisePool } from "../database"; // Adjust the import path as necessary import { v4 as uuidv4 } from "uuid"; export const userService = { async getUserById(userId: string) { const [rows] = await promisePool.query("SELECT * FROM users WHERE id = ?", [ userId, ]); // @ts-ignore return rows[0]; }, async getUserByUsername(username: string) { try { const [rows] = await promisePool.query( "SELECT * FROM users WHERE username = ?", [username], ); // @ts-ignore return rows[0]; } catch (error) { return null; } }, async createUser(username: string) { const id = uuidv4(); await promisePool.query("INSERT INTO users (id, username) VALUES (?, ?)", [ id, username, ]); return { id, username }; }, };

credentialService.ts इस प्रकार दिखता है:

credentialService.ts
import { promisePool } from "../database"; import type { AuthenticatorDevice } from "@simplewebauthn/typescript-types"; export const credentialService = { async saveNewCredential( userId: string, credentialId: string, publicKey: string, counter: number, transports: string, ) { try { await promisePool.query( "INSERT INTO credentials (user_id, credential_id, public_key, counter, transports) VALUES (?, ?, ?, ?, ?)", [userId, credentialId, publicKey, counter, transports], ); } catch (error) { console.error("Error saving new credential:", error); throw error; } }, async getCredentialByCredentialId( credentialId: string, ): Promise<AuthenticatorDevice | null> { try { const [rows] = await promisePool.query( "SELECT * FROM credentials WHERE credential_id = ? LIMIT 1", [credentialId], ); // @ts-ignore if (rows.length === 0) return null; // @ts-ignore const row = rows[0]; return { userID: row.user_id, credentialID: row.credential_id, credentialPublicKey: row.public_key, counter: row.counter, transports: row.transports ? row.transports.split(",") : [], } as AuthenticatorDevice; } catch (error) { console.error("Error retrieving credential:", error); throw error; } }, async updateCredentialCounter(credentialId: string, newCounter: number) { try { await promisePool.query( "UPDATE credentials SET counter = ? WHERE credential_id = ?", [newCounter, credentialId], ); } catch (error) { console.error("Error updating credential counter:", error); throw error; } }, };

5.5 मिडलवेयर#

त्रुटियों को केंद्रीय रूप से संभालने और डिबगिंग को आसान बनाने के लिए, हम एक errorHandler.ts फ़ाइल जोड़ते हैं:

errorHandler.ts
import { Request, Response, NextFunction } from "express"; import { CustomError } from "./customError"; interface ErrorWithStatus extends Error { statusCode?: number; } export const handleError = ( err: CustomError, req: Request, res: Response, next: NextFunction, ) => { const statusCode = err.statusCode || 500; const message = err.message || "Internal Server Error"; console.log(message); res.status(statusCode).send({ error: message }); };

इसके अलावा, हम एक नई customError.ts फ़ाइल जोड़ते हैं क्योंकि हम बाद में बग्स को जल्दी खोजने में मदद करने के लिए कस्टम त्रुटियाँ बनाने में सक्षम होना चाहते हैं:

customError.ts
export class CustomError extends Error { statusCode: number; constructor(message: string, statusCode: number = 500) { super(message); this.statusCode = statusCode; Object.setPrototypeOf(this, CustomError.prototype); } }

5.6 यूटिलिटीज#

utils फ़ोल्डर में, हम दो फाइलें constants.ts और utils.ts बनाते हैं।

constant.ts में कुछ बुनियादी WebAuthn सर्वर जानकारी होती है, जैसे कि रिलाइंग पार्टी का नाम, रिलाइंग पार्टी आईडी और ऑरिजिन:

constant.ts
export const rpName: string = "Passkeys Tutorial"; export const rpID: string = "localhost"; export const origin: string = `http://${rpID}:8080`;

utils.ts में दो फ़ंक्शन हैं जिनकी हमें बाद में डेटा को एन्कोड और डीकोड करने के लिए आवश्यकता होगी:

utils.ts
export const uint8ArrayToBase64 = (uint8Array: Uint8Array): string => Buffer.from(uint8Array).toString("base64"); export const base64ToUint8Array = (base64: string): Uint8Array => new Uint8Array(Buffer.from(base64, "base64"));

5.7 SimpleWebAuthn के साथ पासकी कंट्रोलर्स#

अब, हम अपने बैकएंड के दिल में आते हैं: कंट्रोलर्स। हम दो कंट्रोलर बनाते हैं, एक नई पासकी बनाने के लिए (registration.ts) और एक पासकी के साथ लॉग इन करने के लिए (authentication.ts)।

registration.ts ऐसा दिखता है:

registration.ts
import { generateRegistrationOptions, verifyRegistrationResponse, } from "@simplewebauthn/server"; import { uint8ArrayToBase64 } from "../utils/utils"; import { rpName, rpID, origin } from "../utils/constants"; import { credentialService } from "../services/credentialService"; import { userService } from "../services/userService"; import { RegistrationResponseJSON } from "@simplewebauthn/typescript-types"; import { Request, Response, NextFunction } from "express"; import { CustomError } from "../middleware/customError"; export const handleRegisterStart = async ( req: Request, res: Response, next: NextFunction, ) => { const { username } = req.body; if (!username) { return next(new CustomError("Username empty", 400)); } try { let user = await userService.getUserByUsername(username); if (user) { return next(new CustomError("User already exists", 400)); } else { user = await userService.createUser(username); } const options = await generateRegistrationOptions({ rpName, rpID, userID: user.id, userName: user.username, timeout: 60000, attestationType: "direct", excludeCredentials: [], authenticatorSelection: { residentKey: "preferred", }, // Support for the two most common algorithms: ES256, and RS256 supportedAlgorithmIDs: [-7, -257], }); req.session.loggedInUserId = user.id; req.session.currentChallenge = options.challenge; res.send(options); } catch (error) { next( error instanceof CustomError ? error : new CustomError("Internal Server Error", 500), ); } }; export const handleRegisterFinish = async ( req: Request, res: Response, next: NextFunction, ) => { const { body } = req; const { currentChallenge, loggedInUserId } = req.session; if (!loggedInUserId) { return next(new CustomError("User ID is missing", 400)); } if (!currentChallenge) { return next(new CustomError("Current challenge is missing", 400)); } try { const verification = await verifyRegistrationResponse({ response: body as RegistrationResponseJSON, expectedChallenge: currentChallenge, expectedOrigin: origin, expectedRPID: rpID, requireUserVerification: true, }); if (verification.verified && verification.registrationInfo) { const { credentialPublicKey, credentialID, counter } = verification.registrationInfo; await credentialService.saveNewCredential( loggedInUserId, uint8ArrayToBase64(credentialID), uint8ArrayToBase64(credentialPublicKey), counter, body.response.transports, ); res.send({ verified: true }); } else { next(new CustomError("Verification failed", 400)); } } catch (error) { next( error instanceof CustomError ? error : new CustomError("Internal Server Error", 500), ); } finally { req.session.loggedInUserId = undefined; req.session.currentChallenge = undefined; } };

आइए हमारे कंट्रोलर्स की कार्यक्षमताओं की समीक्षा करें, जो WebAuthn पंजीकरण (साइन-अप) प्रक्रिया में दो प्रमुख समापन बिंदुओं को संभालते हैं। यह वह जगह भी है जहाँ पासवर्ड आधारित प्रमाणीकरण से सबसे बड़ा अंतर है: प्रत्येक रजिस्टर (साइन-अप) या प्रमाणीकरण (लॉगिन) प्रयास के लिए, दो बैकएंड API कॉल की आवश्यकता होती है, जिसके लिए बीच में विशिष्ट फ्रंटएंड सामग्री की आवश्यकता होती है। पासवर्ड को आमतौर पर केवल एक समापन बिंदु की आवश्यकता होती है।

1. handleRegisterStart एंडपॉइंट:

यह एंडपॉइंट फ्रंटएंड द्वारा ट्रिगर किया जाता है, जो एक नया पासकी और खाता बनाने के लिए एक उपयोगकर्ता नाम प्राप्त करता है। इस उदाहरण में, हम केवल एक नया खाता / पासकी बनाने की अनुमति देते हैं यदि कोई खाता पहले से मौजूद नहीं है। वास्तविक दुनिया के अनुप्रयोगों में, आपको इसे इस तरह से संभालना होगा कि उपयोगकर्ताओं को बताया जाए कि एक पासकी पहले से मौजूद है और उसी डिवाइस से जोड़ना संभव नहीं है (लेकिन उपयोगकर्ता किसी प्रकार की पुष्टि के बाद एक अलग डिवाइस से पासकी जोड़ सकता है)। सरलता के लिए, हम इस ट्यूटोरियल में इसे अनदेखा करते हैं।

PublicKeyCredentialCreationOptions तैयार किए जाते हैं। residentKey को पसंदीदा पर सेट किया गया है, और attestationType को डायरेक्ट पर, संभावित डेटाबेस भंडारण के लिए ऑथेंटिकेटर से अधिक डेटा एकत्र करने के लिए।

सामान्य तौर पर, PublicKeyCredentialCreationOptions में निम्नलिखित डेटा होता है:

dictionary PublicKeyCredentialCreationOptions { required PublicKeyCredentialRpEntity rp; required PublicKeyCredentialUserEntity user; required BufferSource challenge; required sequence<PublicKeyCredentialParameters> pubKeyCredParams; unsigned long timeout; sequence<PublicKeyCredentialDescriptor> excludeCredentials = []; AuthenticatorSelectionCriteria authenticatorSelection; DOMString attestation = "none"; AuthenticationExtensionsClientInputs extensions; };
  • rp: रिलाइंग पार्टी (वेबसाइट या सेवा) की जानकारी का प्रतिनिधित्व करता है, जिसमें आमतौर पर इसका नाम (rp.name) और डोमेन (rp.id) शामिल होता है।
  • user: उपयोगकर्ता खाते का विवरण जैसे user.name, user.id, और user.displayName शामिल है।
  • challenge: पंजीकरण प्रक्रिया के दौरान रीप्ले हमलों को रोकने के लिए WebAuthn सर्वर द्वारा बनाया गया एक सुरक्षित, यादृच्छिक मान।
  • pubKeyCredParams: बनाए जाने वाले सार्वजनिक कुंजी क्रेडेंशियल के प्रकार को निर्दिष्ट करता है, जिसमें उपयोग किया गया क्रिप्टोग्राफ़िक एल्गोरिथ्म शामिल है।
  • timeout: वैकल्पिक, उपयोगकर्ता को इंटरैक्शन पूरा करने के लिए मिलीसेकंड में समय निर्धारित करता है।
  • excludeCredentials: बाहर किए जाने वाले क्रेडेंशियल्स की एक सूची; एक ही डिवाइस / ऑथेंटिकेटर के लिए कई बार पासकी पंजीकृत करने से रोकने के लिए उपयोग किया जाता है।
  • authenticatorSelection: ऑथेंटिकेटर का चयन करने के लिए मानदंड, जैसे कि क्या इसे उपयोगकर्ता सत्यापन का समर्थन करना चाहिए या निवासी कुंजियों को कैसे प्रोत्साहित किया जाना चाहिए।
  • attestation: वांछित अटेस्टेशन कन्वेयंस वरीयता को निर्दिष्ट करता है, जैसे "none", "indirect", या "direct"।
  • extensions: वैकल्पिक, अतिरिक्त क्लाइंट एक्सटेंशन की अनुमति देता है।

उपयोगकर्ता आईडी और चुनौती एक सत्र ऑब्जेक्ट में संग्रहीत की जाती है, जो ट्यूटोरियल उद्देश्यों के लिए प्रक्रिया को सरल बनाती है। इसके अलावा, प्रत्येक पंजीकरण (साइन-अप) या प्रमाणीकरण (लॉगिन) प्रयास के बाद सत्र साफ़ हो जाता है।

2. handleRegisterFinish एंडपॉइंट:

यह एंडपॉइंट पहले सेट किए गए उपयोगकर्ता आईडी और चुनौती को पुनः प्राप्त करता है। यह चुनौती के साथ RegistrationResponse को सत्यापित करता है। यदि मान्य है, तो यह उपयोगकर्ता के लिए एक नया क्रेडेंशियल संग्रहीत करता है। एक बार डेटाबेस में संग्रहीत होने के बाद, उपयोगकर्ता आईडी और चुनौती को सत्र से हटा दिया जाता है।

टिप: अपने एप्लिकेशन को डीबग करते समय, हम क्रोम को ब्राउज़र के रूप में उपयोग करने और पासकी आधारित अनुप्रयोगों के डेवलपर अनुभव को बेहतर बनाने के लिए इसकी अंतर्निहित सुविधाओं का उपयोग करने की अत्यधिक अनुशंसा करते हैं, जैसे वर्चुअल WebAuthn ऑथेंटिकेटर और डिवाइस लॉग (अधिक जानकारी के लिए नीचे डेवलपर्स के लिए अतिरिक्त पासकी टिप्स देखें)

इसके बाद, हम authentication.ts पर जाते हैं, जिसकी संरचना और कार्यक्षमता समान है।

authentication.ts ऐसा दिखता है:

authentication.ts
import { Request, Response, NextFunction } from "express"; import { generateAuthenticationOptions, verifyAuthenticationResponse, } from "@simplewebauthn/server"; import { uint8ArrayToBase64, base64ToUint8Array } from "../utils/utils"; import { rpID, origin } from "../utils/constants"; import { credentialService } from "../services/credentialService"; import { userService } from "../services/userService"; import { AuthenticatorDevice } from "@simplewebauthn/typescript-types"; import { isoBase64URL } from "@simplewebauthn/server/helpers"; import { VerifiedAuthenticationResponse, VerifyAuthenticationResponseOpts, } from "@simplewebauthn/server/esm"; import { CustomError } from "../middleware/customError"; export const handleLoginStart = async ( req: Request, res: Response, next: NextFunction, ) => { const { username } = req.body; try { const user = await userService.getUserByUsername(username); if (!user) { return next(new CustomError("User not found", 404)); } req.session.loggedInUserId = user.id; // allowCredentials is purposely for this demo left empty. This causes all existing local credentials // to be displayed for the service instead only the ones the username has registered. const options = await generateAuthenticationOptions({ timeout: 60000, allowCredentials: [], userVerification: "required", rpID, }); req.session.currentChallenge = options.challenge; res.send(options); } catch (error) { next( error instanceof CustomError ? error : new CustomError("Internal Server Error", 500), ); } }; export const handleLoginFinish = async ( req: Request, res: Response, next: NextFunction, ) => { const { body } = req; const { currentChallenge, loggedInUserId } = req.session; if (!loggedInUserId) { return next(new CustomError("User ID is missing", 400)); } if (!currentChallenge) { return next(new CustomError("Current challenge is missing", 400)); } try { const credentialID = isoBase64URL.toBase64(body.rawId); const bodyCredIDBuffer = isoBase64URL.toBuffer(body.rawId); const dbCredential: AuthenticatorDevice | null = await credentialService.getCredentialByCredentialId(credentialID); if (!dbCredential) { return next(new CustomError("Credential not registered with this site", 404)); } // @ts-ignore const user = await userService.getUserById(dbCredential.userID); if (!user) { return next(new CustomError("User not found", 404)); } // @ts-ignore dbCredential.credentialID = base64ToUint8Array(dbCredential.credentialID); // @ts-ignore dbCredential.credentialPublicKey = base64ToUint8Array( dbCredential.credentialPublicKey, ); let verification: VerifiedAuthenticationResponse; const opts: VerifyAuthenticationResponseOpts = { response: body, expectedChallenge: currentChallenge, expectedOrigin: origin, expectedRPID: rpID, authenticator: dbCredential, }; verification = await verifyAuthenticationResponse(opts); const { verified, authenticationInfo } = verification; if (verified) { await credentialService.updateCredentialCounter( uint8ArrayToBase64(bodyCredIDBuffer), authenticationInfo.newCounter, ); res.send({ verified: true }); } else { next(new CustomError("Verification failed", 400)); } } catch (error) { next( error instanceof CustomError ? error : new CustomError("Internal Server Error", 500), ); } finally { req.session.currentChallenge = undefined; req.session.loggedInUserId = undefined; } };

हमारी प्रमाणीकरण (लॉगिन) प्रक्रिया में दो समापन बिंदु शामिल हैं:

1. handleLoginStart एंडपॉइंट:

यह एंडपॉइंट तब सक्रिय होता है जब कोई उपयोगकर्ता लॉग इन करने का प्रयास करता है। यह पहले जांचता है कि उपयोगकर्ता नाम डेटाबेस में मौजूद है या नहीं, यदि नहीं मिला तो एक त्रुटि लौटाता है। एक वास्तविक दुनिया के परिदृश्य में, आप इसके बजाय एक नया खाता बनाने की पेशकश कर सकते हैं।

मौजूदा उपयोगकर्ताओं के लिए, यह डेटाबेस से उपयोगकर्ता आईडी पुनः प्राप्त करता है, इसे सत्र में संग्रहीत करता है, और PublicKeyCredentialRequestOptions विकल्प उत्पन्न करता है। क्रेडेंशियल उपयोग को प्रतिबंधित करने से बचने के लिए allowCredentials को खाली छोड़ दिया गया है। इसीलिए इस रिलाइंग पार्टी के लिए सभी उपलब्ध पासकी को पासकी मोडल में चुना जा सकता है।

उत्पन्न चुनौती को भी सत्र में संग्रहीत किया जाता है और PublicKeyCredentialRequestOptions को फ्रंटएंड पर वापस भेज दिया जाता है।

PublicKeyCredentialRequestOptions में निम्नलिखित डेटा होता है:

dictionary PublicKeyCredentialRequestOptions { required BufferSource challenge; unsigned long timeout; USVString rpId; sequence<PublicKeyCredentialDescriptor> allowCredentials = []; DOMString userVerification = "preferred"; AuthenticationExtensionsClientInputs extensions; };
  • challenge: WebAuthn सर्वर से एक सुरक्षित, यादृच्छिक मान जो प्रमाणीकरण प्रक्रिया के दौरान रीप्ले हमलों को रोकने के लिए उपयोग किया जाता है।
  • timeout: वैकल्पिक, उपयोगकर्ता को प्रमाणीकरण अनुरोध का जवाब देने के लिए मिलीसेकंड में समय निर्धारित करता है।
  • rpId: रिलाइंग पार्टी आईडी, आमतौर पर सेवा का डोमेन।
  • allowCredentials: क्रेडेंशियल डिस्क्रिप्टर की एक वैकल्पिक सूची, यह निर्दिष्ट करती है कि इस प्रमाणीकरण (लॉगिन) के लिए कौन से क्रेडेंशियल का उपयोग किया जा सकता है।
  • userVerification: उपयोगकर्ता सत्यापन की आवश्यकता को निर्दिष्ट करता है, जैसे "required", "preferred", या "discouraged"।
  • extensions: वैकल्पिक, अतिरिक्त क्लाइंट एक्सटेंशन की अनुमति देता है।

2. handleLoginFinish एंडपॉइंट:

यह एंडपॉइंट सत्र से currentChallenge और loggedInUserId को पुनः प्राप्त करता है।

यह बॉडी से क्रेडेंशियल आईडी का उपयोग करके सही क्रेडेंशियल के लिए डेटाबेस से पूछताछ करता है। यदि क्रेडेंशियल मिल जाता है, तो इसका मतलब है कि इस क्रेडेंशियल आईडी से जुड़े उपयोगकर्ता को अब प्रमाणित (लॉग इन) किया जा सकता है। फिर, हम क्रेडेंशियल से प्राप्त उपयोगकर्ता आईडी के माध्यम से उपयोगकर्ता तालिका से उपयोगकर्ता से पूछताछ कर सकते हैं और चुनौती और अनुरोध बॉडी का उपयोग करके authenticationResponse को सत्यापित कर सकते हैं। यदि सब कुछ सफल होता है, तो हम लॉगिन सफलता संदेश दिखाते हैं। यदि कोई मेल खाने वाला क्रेडेंशियल नहीं मिलता है, तो एक त्रुटि भेजी जाती है।

इसके अतिरिक्त, यदि सत्यापन सफल होता है, तो क्रेडेंशियल का काउंटर अपडेट हो जाता है, उपयोग की गई चुनौती और loggedInUserId को सत्र से हटा दिया जाता है।

इसके अलावा, हम src/app और src/constant फ़ोल्डर को उसमें मौजूद सभी फ़ाइलों के साथ हटा सकते हैं।

नोट: उचित सत्र प्रबंधन और रूट सुरक्षा, जो वास्तविक जीवन के अनुप्रयोगों में महत्वपूर्ण हैं, इस ट्यूटोरियल में सरलता के लिए छोड़ दिए गए हैं।

5.8 पासकी रूट्स#

अंतिम लेकिन कम से कम नहीं, हमें यह सुनिश्चित करने की आवश्यकता है कि हमारे कंट्रोलर routes.ts में उपयुक्त रूट जोड़कर पहुंच योग्य हैं जो एक नई डायरेक्टरी src/routes में है:

routes.ts
import express from "express"; import { handleError } from "../middleware/errorHandler"; import { handleRegisterStart, handleRegisterFinish } from "../controllers/registration"; import { handleLoginStart, handleLoginFinish } from "../controllers/authentication"; const router = express.Router(); router.post("/registerStart", handleRegisterStart); router.post("/registerFinish", handleRegisterFinish); router.post("/loginStart", handleLoginStart); router.post("/loginFinish", handleLoginFinish); router.use(handleError); export { router };
Substack Icon

Subscribe to our Passkeys Substack for the latest news.

Subscribe

6. फ्रंटएंड में पासकी को एकीकृत करें#

पासकी ट्यूटोरियल का यह हिस्सा इस बात पर केंद्रित है कि आपके एप्लिकेशन के फ्रंटएंड में पासकी का समर्थन कैसे करें। हमारे पास एक बहुत ही बुनियादी फ्रंटएंड है जिसमें तीन फाइलें हैं: index.html, styles.css और script.js। सभी तीन फाइलें एक नई src/public फ़ोल्डर में हैं।

index.html फ़ाइल में उपयोगकर्ता नाम के लिए एक इनपुट फ़ील्ड और रजिस्टर और लॉगिन करने के लिए दो बटन हैं। इसके अलावा, हम @simplewebauthn/browser स्क्रिप्ट आयात करते हैं जो js/script.js फ़ाइल में ब्राउज़र Web Authentication API के साथ इंटरैक्शन को सरल बनाती है।

index.html ऐसा दिखता है:

index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Passkey Tutorial</title> <link rel="stylesheet" href="css/style.css" /> </head> <body> <div class="container"> <h1>Passkey Tutorial</h1> <div id="message"></div> <div class="input-group"> <input type="text" id="username" placeholder="Enter username" /> <button id="registerButton">Register</button> <button id="loginButton">Login</button> </div> </div> <script src="https://unpkg.com/@simplewebauthn/browser/dist/bundle/index.es5.umd.min.js"></script> <script src="js/script.js"></script> </body> </html>

script.js इस प्रकार दिखता है:

script.js
document.getElementById("registerButton").addEventListener("click", register); document.getElementById("loginButton").addEventListener("click", login); function showMessage(message, isError = false) { const messageElement = document.getElementById("message"); messageElement.textContent = message; messageElement.style.color = isError ? "red" : "green"; } async function register() { // Retrieve the username from the input field const username = document.getElementById("username").value; try { // Get registration options from your server. Here, we also receive the challenge. const response = await fetch("/api/passkey/registerStart", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: username }), }); console.log(response); // Check if the registration options are ok. if (!response.ok) { throw new Error( "User already exists or failed to get registration options from server", ); } // Convert the registration options to JSON. const options = await response.json(); console.log(options); // This triggers the browser to display the passkey / WebAuthn modal (e.g. Face ID, Touch ID, Windows Hello). // A new attestation is created. This also means a new public-private-key pair is created. const attestationResponse = await SimpleWebAuthnBrowser.startRegistration(options); // Send attestationResponse back to server for verification and storage. const verificationResponse = await fetch("/api/passkey/registerFinish", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(attestationResponse), }); if (verificationResponse.ok) { showMessage("Registration successful"); } else { showMessage("Registration failed", true); } } catch (error) { showMessage("Error: " + error.message, true); } } async function login() { // Retrieve the username from the input field const username = document.getElementById("username").value; try { // Get login options from your server. Here, we also receive the challenge. const response = await fetch("/api/passkey/loginStart", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: username }), }); // Check if the login options are ok. if (!response.ok) { throw new Error("Failed to get login options from server"); } // Convert the login options to JSON. const options = await response.json(); console.log(options); // This triggers the browser to display the passkey / WebAuthn modal (e.g. Face ID, Touch ID, Windows Hello). // A new assertionResponse is created. This also means that the challenge has been signed. const assertionResponse = await SimpleWebAuthnBrowser.startAuthentication(options); // Send assertionResponse back to server for verification. const verificationResponse = await fetch("/api/passkey/loginFinish", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(assertionResponse), }); if (verificationResponse.ok) { showMessage("Login successful"); } else { showMessage("Login failed", true); } } catch (error) { showMessage("Error: " + error.message, true); } }

script.js में, तीन प्राथमिक कार्य हैं:

1. showMessage फ़ंक्शन:

यह एक उपयोगिता फ़ंक्शन है जिसका उपयोग मुख्य रूप से त्रुटि संदेश प्रदर्शित करने के लिए किया जाता है, जो डिबगिंग में सहायता करता है।

2. रजिस्टर फ़ंक्शन:

जब उपयोगकर्ता "रजिस्टर" पर क्लिक करता है तो यह ट्रिगर होता है। यह इनपुट फ़ील्ड से उपयोगकर्ता नाम निकालता है और इसे passkeyRegisterStart एंडपॉइंट पर भेजता है। प्रतिक्रिया में PublicKeyCredentialCreationOptions शामिल हैं, जिन्हें JSON में परिवर्तित किया जाता है और SimpleWebAuthnBrowser.startRegistration को पास किया जाता है। यह कॉल डिवाइस ऑथेंटिकेटर (जैसे फेस आईडी या टच आईडी) को सक्रिय करती है। सफल स्थानीय प्रमाणीकरण पर, हस्ताक्षरित चुनौती को passkeyRegisterFinish एंडपॉइंट पर वापस भेज दिया जाता है, जिससे पासकी निर्माण प्रक्रिया पूरी हो जाती है।

रजिस्टर (साइन-अप) प्रक्रिया के दौरान, अटेस्टेशन ऑब्जेक्ट एक महत्वपूर्ण भूमिका निभाता है, तो आइए इस पर करीब से नज़र डालें।

अटेस्टेशन ऑब्जेक्ट में मुख्य रूप से तीन घटक होते हैं: fmt, attStmt, और authDatafmt तत्व अटेस्टेशन स्टेटमेंट के प्रारूप को दर्शाता है, जबकि attStmt वास्तविक अटेस्टेशन स्टेटमेंट का प्रतिनिधित्व करता है। उन परिदृश्यों में जहां अटेस्टेशन को अनावश्यक माना जाता है, fmt को "none" के रूप में नामित किया जाएगा, जिससे एक खाली attStmt होगा।

इस संरचना के भीतर authData खंड पर ध्यान केंद्रित किया गया है। यह खंड हमारे सर्वर पर रिलाइंग पार्टी आईडी, फ्लैग, काउंटर और अटेस्टेड क्रेडेंशियल डेटा जैसे आवश्यक तत्वों को पुनः प्राप्त करने के लिए महत्वपूर्ण है। फ्लैग के संबंध में, विशेष रुचि के BS (बैकअप स्टेट) और BE (बैकअप एलिजिबिलिटी) हैं जो अधिक जानकारी प्रदान करते हैं कि क्या एक पासकी सिंक की गई है (जैसे iCloud Keychain या 1Password के माध्यम से)। इसके अलावा, UV (उपयोगकर्ता सत्यापन) और UP (उपयोगकर्ता उपस्थिति) अधिक उपयोगी जानकारी प्रदान करते हैं।

यह ध्यान रखना महत्वपूर्ण है कि अटेस्टेशन ऑब्जेक्ट के विभिन्न हिस्से, जिसमें ऑथेंटिकेटर डेटा, रिलाइंग पार्टी आईडी और अटेस्टेशन स्टेटमेंट शामिल हैं, या तो हैश किए गए हैं या ऑथेंटिकेटर द्वारा इसकी निजी कुंजी का उपयोग करके डिजिटल रूप से हस्ताक्षरित हैं। यह प्रक्रिया अटेस्टेशन ऑब्जेक्ट की समग्र अखंडता को बनाए रखने के लिए अभिन्न है।

3. लॉगिन फ़ंक्शन:

जब उपयोगकर्ता "लॉगिन" पर क्लिक करता है तो यह सक्रिय होता है। रजिस्टर फ़ंक्शन के समान, यह उपयोगकर्ता नाम निकालता है और इसे passkeyLoginStart एंडपॉइंट पर भेजता है। प्रतिक्रिया, जिसमें PublicKeyCredentialRequestOptions शामिल हैं, को JSON में परिवर्तित किया जाता है और SimpleWebAuthnBrowser.startAuthentication के साथ उपयोग किया जाता है। यह डिवाइस पर स्थानीय प्रमाणीकरण को ट्रिगर करता है। हस्ताक्षरित चुनौती को फिर passkeyLoginFinish एंडपॉइंट पर वापस भेज दिया जाता है। इस एंडपॉइंट से एक सफल प्रतिक्रिया इंगित करती है कि उपयोगकर्ता ने ऐप में सफलतापूर्वक लॉग इन कर लिया है।

इसके अतिरिक्त, साथ वाली CSS फ़ाइल एप्लिकेशन के लिए सरल स्टाइलिंग प्रदान करती है:

body { font-family: "Helvetica Neue", Arial, sans-serif; text-align: center; padding: 40px; background-color: #f3f4f6; color: #333; } .container { max-width: 400px; margin: auto; background: white; padding: 20px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); border-radius: 8px; } h1 { color: #007bff; font-size: 24px; margin-bottom: 20px; } .input-group { margin-bottom: 20px; } input[type="text"] { padding: 10px; margin-bottom: 10px; border: 1px solid #ced4da; border-radius: 4px; width: calc(100% - 22px); } button { width: calc(50% - 20px); padding: 10px 0; margin: 5px; font-size: 16px; cursor: pointer; border: none; border-radius: 4px; background-color: #007bff; color: white; } button:hover { background-color: #0056b3; } #message { color: #dc3545; margin: 20px; }

7. पासकी उदाहरण ऐप चलाएँ#

अपने एप्लिकेशन को क्रिया में देखने के लिए, अपने TypeScript कोड को इसके साथ संकलित और चलाएँ:

npm run dev

आपका सर्वर अब http://localhost:8080 पर चालू होना चाहिए।

उत्पादन के लिए विचार:

याद रखें, हमने जो कवर किया है वह एक बुनियादी रूपरेखा है। उत्पादन वातावरण में एक पासकी एप्लिकेशन को तैनात करते समय, आपको इसमें गहराई से उतरने की आवश्यकता है:

  • सुरक्षा उपाय: उपयोगकर्ता डेटा की सुरक्षा के लिए मजबूत सुरक्षा प्रथाओं को लागू करें।
  • त्रुटि प्रबंधन: सुनिश्चित करें कि आपका एप्लिकेशन त्रुटियों को शालीनता से संभालता और लॉग करता है।
  • डेटाबेस प्रबंधन: मापनीयता और विश्वसनीयता के लिए डेटाबेस संचालन का अनुकूलन करें।

8. पासकी DevOps एकीकरण#

हमने अपने डेटाबेस के लिए पहले ही एक Docker कंटेनर स्थापित कर लिया है। अगला, हम अपने Docker Compose सेटअप का विस्तार करेंगे ताकि बैकएंड और फ्रंटएंड दोनों के साथ सर्वर शामिल हो सके। आपकी docker-compose.yml फ़ाइल को तदनुसार अद्यतन किया जाना चाहिए।

हमारे एप्लिकेशन को कंटेनरीकृत करने के लिए, हम एक नई Dockerfile बनाते हैं जो आवश्यक पैकेजों को स्थापित करती है और विकास सर्वर शुरू करती है:

Docker
# Use an official Node runtime as a parent image FROM node:20-alpine # Set the working directory in the container WORKDIR /usr/src/app # Copy package.json and package-lock.json COPY package*.json ./ # Install any needed packages RUN npm install # Bundle your app's source code inside the Docker image COPY . . # Make port 8080 available to the world outside this container EXPOSE 8080 # Define the command to run your app CMD ["npm", "run", "dev"]

फिर, हम इस कंटेनर को शुरू करने के लिए docker-compose.yml फ़ाइल का भी विस्तार करते हैं:

docker-compose.yml
version: "3.1" services: db: image: mysql command: --default-authentication-plugin=mysql_native_password restart: always environment: MYSQL_ROOT_PASSWORD: my-secret-pw MYSQL_DATABASE: webauthn_db ports: - "3306:3306" volumes: - ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql app: build: . ports: - "8080:8080" environment: - DB_HOST=db - DB_USER=root - DB_PASSWORD=my-secret-pw - DB_NAME=webauthn_db - SESSION_SECRET=secret123 depends_on: - db

यदि आप अब अपने टर्मिनल में docker compose up चलाते हैं और http://localhost:8080 पर पहुँचते हैं, तो आपको अपने पासकी वेब ऐप का कार्यशील संस्करण देखना चाहिए (यहाँ Windows 11 23H2 + Chrome 119 पर चल रहा है):

9. डेवलपर्स के लिए अतिरिक्त पासकी टिप्स#

चूंकि हम काफी समय से पासकी कार्यान्वयन के साथ काम कर रहे हैं, इसलिए यदि आप वास्तविक जीवन के पासकी ऐप्स पर काम करते हैं तो हमें कुछ चुनौतियों का सामना करना पड़ा है:

  • डिवाइस / प्लेटफ़ॉर्म संगतता और समर्थन
  • उपयोगकर्ता ऑनबोर्डिंग और शिक्षा
  • खोए हुए या बदले हुए उपकरणों को संभालना
  • क्रॉस-प्लेटफ़ॉर्म प्रमाणीकरण
  • फ़ॉलबैक तंत्र
  • एन्कोडिंग जटिलता: एन्कोडिंग अक्सर सबसे कठिन हिस्सा होता है क्योंकि आपको JSON, CBOR, uint8arrays, बफ़र्स, ब्लॉब्स, विभिन्न डेटाबेस, base64 और base64url से निपटना पड़ता है जहाँ बहुत सारी त्रुटियाँ हो सकती हैं
  • पासकी प्रबंधन (जैसे पासकी जोड़ने, हटाने या नाम बदलने के लिए)

इसके अलावा, हमारे पास डेवलपर्स के लिए निम्नलिखित टिप्स हैं जब कार्यान्वयन भाग की बात आती है:

पासकी डीबगर का उपयोग करें

पासकी डीबगर विभिन्न WebAuthn सर्वर सेटिंग्स और क्लाइंट प्रतिक्रियाओं का परीक्षण करने में मदद करता है। इसके अलावा, यह ऑथेंटिकेटर प्रतिक्रियाओं के लिए एक बेहतरीन पार्सर प्रदान करता है।

क्रोम डिवाइस लॉग फ़ीचर के साथ डीबग करें

FIDO/WebAuthn कॉल्स की निगरानी के लिए क्रोम के डिवाइस लॉग (chrome://device-log/ के माध्यम से सुलभ) का उपयोग करें। यह सुविधा प्रमाणीकरण (लॉगिन) प्रक्रिया के वास्तविक समय के लॉग प्रदान करती है, जिससे आप आदान-प्रदान किए जा रहे डेटा को देख सकते हैं और उत्पन्न होने वाली किसी भी समस्या का निवारण कर सकते हैं।

क्रोम में अपनी सभी पासकी प्राप्त करने के लिए एक और बहुत उपयोगी शॉर्टकट chrome://settings/passkeys का उपयोग करना है।

क्रोम वर्चुअल WebAuthn ऑथेंटिकेटर का उपयोग करें

विकास के दौरान टच आईडी, फेस आईडी या विंडोज हैलो प्रॉम्प्ट का उपयोग करने से बचने के लिए, क्रोम एक बहुत ही आसान वर्चुअल WebAuthn ऑथेंटिकेटर के साथ आता है जो एक वास्तविक ऑथेंटिकेटर का अनुकरण करता है। हम चीजों को गति देने के लिए इसका उपयोग करने की अत्यधिक अनुशंसा करते हैं। अधिक विवरण यहाँ प्राप्त करें।

विभिन्न प्लेटफ़ॉर्म और ब्राउज़रों पर परीक्षण करें

विभिन्न ब्राउज़रों और प्लेटफ़ॉर्म पर संगतता और कार्यक्षमता सुनिश्चित करें। WebAuthn विभिन्न ब्राउज़रों पर अलग-अलग व्यवहार करता है, इसलिए पूरी तरह से परीक्षण महत्वपूर्ण है।

विभिन्न उपकरणों पर परीक्षण करें

यहाँ ngrok जैसे उपकरणों के साथ काम करना विशेष रूप से उपयोगी है, जहाँ आप अपने स्थानीय एप्लिकेशन को अन्य (मोबाइल) उपकरणों पर पहुँच योग्य बना सकते हैं।

उपयोगकर्ता सत्यापन को पसंदीदा पर सेट करें

PublicKeyCredentialRequestOptions में userVerification के लिए गुणों को परिभाषित करते समय, उन्हें पसंदीदा पर सेट करना चुनें क्योंकि यह उपयोगिता और सुरक्षा के बीच एक अच्छा संतुलन है। इसका मतलब है कि उपयुक्त उपकरणों पर सुरक्षा जाँचें होती हैं जबकि बिना बायोमेट्रिक क्षमताओं वाले उपकरणों पर उपयोगकर्ता-मित्रता बनी रहती है।

10. निष्कर्ष: पासकी ट्यूटोरियल#

हमें उम्मीद है कि यह पासकी ट्यूटोरियल पासकी को प्रभावी ढंग से कैसे लागू किया जाए, इसकी स्पष्ट समझ प्रदान करता है। ट्यूटोरियल के दौरान, हमने एक पासकी एप्लिकेशन बनाने के लिए आवश्यक चरणों के माध्यम से चला है, जिसमें मौलिक अवधारणाओं और व्यावहारिक कार्यान्वयन पर ध्यान केंद्रित किया गया है। जबकि यह गाइड एक प्रारंभिक बिंदु के रूप में कार्य करता है, WebAuthn की दुनिया में तलाशने और परिष्कृत करने के लिए बहुत कुछ है।

हम डेवलपर्स को पासकी की बारीकियों में गहराई से उतरने के लिए प्रोत्साहित करते हैं (जैसे कई पासकी जोड़ना, उपकरणों पर पासकी-तत्परता की जाँच करना या पुनर्प्राप्ति समाधान प्रदान करना)। यह एक यात्रा है जिस पर लगना सार्थक है, जो उपयोगकर्ता प्रमाणीकरण को बढ़ाने में चुनौतियों और अपार पुरस्कार दोनों प्रदान करता है। पासकी के साथ, आप केवल एक सुविधा नहीं बना रहे हैं; आप एक अधिक सुरक्षित और उपयोगकर्ता-अनुकूल डिजिटल दुनिया में योगदान दे रहे हैं।

Add passkeys to your app in <1 hour with our UI components, SDKs & guides.

Start for free

Share this article


LinkedInTwitterFacebook

Enjoyed this read?

🤝 Join our Passkeys Community

Share passkeys implementation tips and get support to free the world from passwords.

🚀 Subscribe to Substack

Get the latest news, strategies, and insights about passkeys sent straight to your inbox.

Related Articles

Table of Contents