सर्वर-साइड पासकी रजिस्ट्रेशन

खास जानकारी

पासकी को रजिस्टर करने के मुख्य चरणों के बारे में यहां खास जानकारी दी गई है:

पासकी रजिस्टर करने की प्रोसेस

  • पासकी बनाने के लिए विकल्प तय करें. इन्हें क्लाइंट को भेजें, ताकि इन्हें पासकी बनाने के लिए किए जाने वाले कॉल में पास किया जा सके: वेब पर WebAuthn API कॉल navigator.credentials.create और Android पर credentialManager.createCredential. जब उपयोगकर्ता पासकी बनाने की पुष्टि कर देता है, तब पासकी बनाने का कॉल रिज़ॉल्व हो जाता है और क्रेडेंशियल PublicKeyCredential मिलता है.
  • क्रेडेंशियल की पुष्टि करना और उसे सर्वर पर सेव करना.

यहां दिए गए सेक्शन में, हर चरण के बारे में ज़्यादा जानकारी दी गई है.

क्रेडेंशियल बनाने के विकल्प बनाना

सर्वर पर आपको सबसे पहले, PublicKeyCredentialCreationOptions ऑब्जेक्ट बनाना होगा.

इसके लिए, FIDO सर्वर-साइड लाइब्रेरी का इस्तेमाल करें. आम तौर पर, यह एक यूटिलिटी फ़ंक्शन उपलब्ध कराएगा, जो आपके लिए ये विकल्प बना सकता है. उदाहरण के लिए, SimpleWebAuthn generateRegistrationOptions उपलब्ध कराता है.

PublicKeyCredentialCreationOptions में पासकी बनाने के लिए ज़रूरी सभी चीज़ें शामिल होनी चाहिए: उपयोगकर्ता और आरपी के बारे में जानकारी. साथ ही, बनाए जा रहे क्रेडेंशियल की प्रॉपर्टी के लिए कॉन्फ़िगरेशन. इन सभी को तय करने के बाद, ज़रूरत के हिसाब से उन्हें अपने FIDO सर्वर-साइड लाइब्रेरी में मौजूद फ़ंक्शन को पास करें. यह फ़ंक्शन, PublicKeyCredentialCreationOptions ऑब्जेक्ट बनाने के लिए ज़िम्मेदार होता है.

PublicKeyCredentialCreationOptions के कुछ फ़ील्ड, कॉन्सटैंट हो सकते हैं. अन्य को सर्वर पर डाइनैमिक तौर पर तय किया जाना चाहिए:

  • rpId: सर्वर पर आरपी आईडी भरने के लिए, सर्वर-साइड फ़ंक्शन या वैरिएबल का इस्तेमाल करें. इनसे आपको अपने वेब ऐप्लिकेशन का होस्टनेम मिलता है, जैसे कि example.com.
  • user.name और user.displayName: इन फ़ील्ड को भरने के लिए, साइन इन किए हुए उपयोगकर्ता के सेशन की जानकारी का इस्तेमाल करें. इसके अलावा, अगर उपयोगकर्ता साइन अप करते समय पासकी बना रहा है, तो नए उपयोगकर्ता खाते की जानकारी का इस्तेमाल करें. user.name आम तौर पर एक ईमेल पता होता है और यह आरपी के लिए यूनीक होता है. user.displayName, उपयोगकर्ता के लिए आसान नाम है. ध्यान दें कि सभी प्लैटफ़ॉर्म displayName का इस्तेमाल नहीं करेंगे.
  • user.id: खाता बनाने पर जनरेट होने वाली रैंडम और यूनीक स्ट्रिंग. यह स्थायी होना चाहिए, न कि उपयोगकर्ता नाम की तरह जिसमें बदलाव किया जा सकता है. यूज़र आईडी से किसी खाते की पहचान की जाती है. हालांकि, इसमें व्यक्तिगत पहचान से जुड़ी कोई जानकारी (पीआईआई) शामिल नहीं होनी चाहिए. ऐसा हो सकता है कि आपके सिस्टम में पहले से ही कोई यूज़र आईडी मौजूद हो. हालांकि, अगर ज़रूरी हो, तो पासकी के लिए खास तौर पर एक यूज़र आईडी बनाएं, ताकि उसमें व्यक्तिगत पहचान से जुड़ी कोई जानकारी शामिल न हो.
  • excludeCredentials: मौजूदा क्रेडेंशियल के आईडी की सूची, ताकि पासकी उपलब्ध कराने वाली सेवा से डुप्लीकेट पासकी न बनाई जा सके. इस फ़ील्ड को भरने के लिए, अपने डेटाबेस में इस उपयोगकर्ता के मौजूदा क्रेडेंशियल देखें. अगर पहले से कोई पासकी मौजूद है, तो नई पासकी बनाने से रोकें पर जाकर जानकारी देखें.
  • challenge: क्रेडेंशियल रजिस्ट्रेशन के लिए, चैलेंज तब तक काम का नहीं होता, जब तक अटेस्टेशन का इस्तेमाल न किया जाए. यह पासकी की सेवा देने वाली कंपनी की पहचान और उससे मिलने वाले डेटा की पुष्टि करने की एक बेहतर तकनीक है. हालांकि, अगर पुष्टि करने की सुविधा का इस्तेमाल नहीं किया जाता है, तब भी चैलेंज फ़ील्ड भरना ज़रूरी है. पुष्टि करने के लिए सुरक्षित चैलेंज बनाने के निर्देश, सर्वर-साइड पासकी ऑथेंटिकेशन में उपलब्ध हैं.

एन्कोडिंग और डिकोडिंग

सर्वर से भेजा गया PublicKeyCredentialCreationOptions
PublicKeyCredentialCreationOptions सर्वर से भेजा गया. challenge और user.id जैसे बाइनरी फ़ील्ड, सर्वर-साइड पर base64URL में एन्कोड किए जाते हैं, ताकि विकल्पों को एचटीटीपीएस पर JSON के तौर पर डिलीवर किया जा सके.

स्टैंडर्ड WebAuthn PublicKeyCredentialCreationOptions ऑब्जेक्ट में ऐसे फ़ील्ड शामिल होते हैं जो ArrayBuffer होते हैं. इन्हें JSON.stringify() के साथ सीधे तौर पर सीरियलाइज़ नहीं किया जा सकता. इन विकल्पों को एचटीटीपीएस पर भेजने के लिए, बाइनरी फ़ील्ड को base64URL स्ट्रिंग के तौर पर कोड में बदलना ज़रूरी है:

  • सर्वर पर, आपकी FIDO सर्वर-साइड लाइब्रेरी (जैसे, SimpleWebAuthn), Base64URL-एन्कोडेड फ़ील्ड के साथ JSON-सीरियलाइज़ेबल विकल्प ऑब्जेक्ट (जैसे, PublicKeyCredentialCreationOptionsJSON) अपने-आप जनरेट करती है.
  • क्लाइंट पर, मॉडर्न ब्राउज़र PublicKeyCredential.parseCreationOptionsFromJSON() के साथ काम करते हैं, ताकि JSON विकल्पों को सीधे PublicKeyCredentialCreationOptions में बदला जा सके. @simplewebauthn/browser जैसी क्लाइंट-साइड लाइब्रेरी का इस्तेमाल करने पर, startRegistration({ optionsJSON: options }) जैसे फ़ंक्शन इस कन्वर्ज़न को अपने-आप मैनेज करते हैं.

कोड का उदाहरण: क्रेडेंशियल बनाने के विकल्प सेट अप करना

हम अपने उदाहरणों में SimpleWebAuthn लाइब्रेरी का इस्तेमाल कर रहे हैं. यहां हम सार्वजनिक पासकोड क्रेडेंशियल के विकल्प बनाने की ज़िम्मेदारी, इसके generateRegistrationOptions फ़ंक्शन को सौंपते हैं.

import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
} from '@simplewebauthn/server';
import { isoBase64URL, isoUint8Array } from '@simplewebauthn/server/helpers';

router.post('/registerRequest', csrfCheck, sessionCheck, async (req, res) => {
  const { user } = res.locals;
  // Ensure you nest verification function calls in try/catch blocks.
  // If something fails, throw an error with a descriptive error message.
  // Return that message with an appropriate error code to the client.
  try {
    // `excludeCredentials` prevents users from re-registering existing
    // credentials for a given passkey provider
    const excludeCredentials = [];
    const credentials = Credentials.findByUserId(user.id);
    if (credentials.length > 0) {
      for (const cred of credentials) {
        excludeCredentials.push({
          id: cred.id,
          type: 'public-key',
          transports: cred.transports,
        });
      }
    }

    // Generate registration options for WebAuthn create
    const options = await generateRegistrationOptions({
      rpName: process.env.RP_NAME,
      rpID: process.env.HOSTNAME,
      userID: isoUint8Array.fromUTF8String(user.id),
      userName: user.username,
      userDisplayName: user.displayName || '',
      attestationType: 'none',
      excludeCredentials,
      authenticatorSelection: {
        authenticatorAttachment: 'platform',
        residentKey: 'required',
        userVerification: 'preferred',
      },
    });

    // Keep the challenge in the session
    req.session.challenge = options.challenge;

    return res.json(options);
  } catch (e) {
    console.error(e);
    return res.status(400).send({ error: e.message });
  }
});

सार्वजनिक पासकोड को सेव करना

सर्वर से भेजा गया PublicKeyCredentialCreationOptions
navigator.credentials.create, PublicKeyCredential ऑब्जेक्ट दिखाता है.

जब क्लाइंट पर navigator.credentials.create को सही तरीके से रिज़ॉल्व कर लिया जाता है, तो इसका मतलब है कि पासकी बना ली गई है. PublicKeyCredential ऑब्जेक्ट दिखाया जाता है.

PublicKeyCredential ऑब्जेक्ट में एक AuthenticatorAttestationResponse ऑब्जेक्ट होता है. यह ऑब्जेक्ट, पासकी बनाने के लिए क्लाइंट के निर्देश पर, पासकी की सेवा देने वाली कंपनी के जवाब को दिखाता है. इसमें नए क्रेडेंशियल के बारे में जानकारी होती है. बाद में उपयोगकर्ता की पुष्टि करने के लिए, आपको आरपी के तौर पर इसकी ज़रूरत होती है. AuthenticatorAttestationResponse के बारे में ज़्यादा जानने के लिए, परिशिष्ट: AuthenticatorAttestationResponse पर जाएं.

PublicKeyCredential ऑब्जेक्ट को सर्वर पर भेजें. कोड मिलने के बाद, उसकी पुष्टि करें.

पुष्टि करने के इस चरण को अपने FIDO सर्वर-साइड लाइब्रेरी को सौंपें. आम तौर पर, इसके लिए एक यूटिलिटी फ़ंक्शन उपलब्ध कराया जाता है. उदाहरण के लिए, SimpleWebAuthn verifyRegistrationResponse उपलब्ध कराता है. परिशिष्ट: रजिस्ट्रेशन के जवाब की पुष्टि में जाकर, बैकग्राउंड में होने वाली प्रोसेस के बारे में जानें.

पुष्टि होने के बाद, क्रेडेंशियल की जानकारी को अपने डेटाबेस में सेव करें, ताकि उपयोगकर्ता बाद में उस क्रेडेंशियल से जुड़ी पासकी से पुष्टि कर सके.

पासकी से जुड़े सार्वजनिक पासकोड क्रेडेंशियल के लिए, एक खास टेबल का इस्तेमाल करें. किसी उपयोगकर्ता के पास सिर्फ़ एक पासवर्ड हो सकता है, लेकिन उसके पास कई पासकी हो सकती हैं. उदाहरण के लिए, Apple iCloud Keychain और Google Password Manager के ज़रिए सिंक की गई पासकी.

यहां एक स्कीमा का उदाहरण दिया गया है. इसका इस्तेमाल करके, क्रेडेंशियल की जानकारी को सेव किया जा सकता है:

पासकी के लिए डेटाबेस स्कीमा

  • उपयोगकर्ता टेबल:
    • user_id: प्राइमरी यूज़र आईडी. उपयोगकर्ता के लिए रैंडम, यूनीक, और स्थायी आईडी. इसे अपनी उपयोगकर्ता टेबल के लिए, प्राथमिक कुंजी के तौर पर इस्तेमाल करें.
    • username. उपयोगकर्ता के हिसाब से तय किया गया उपयोगकर्ता नाम, जिसमें बदलाव किया जा सकता है.
    • passkey_user_id: पासकी के लिए खास तौर पर बनाया गया यूज़र आईडी, जिसमें व्यक्तिगत पहचान से जुड़ी जानकारी शामिल नहीं होती. इसे आपके रजिस्ट्रेशन के विकल्पों में user.id से दिखाया जाता है. जब उपयोगकर्ता बाद में पुष्टि करने की कोशिश करेगा, तब पुष्टि करने वाला ऐप्लिकेशन इस passkey_user_id को userHandle में पुष्टि करने के अपने जवाब में उपलब्ध कराएगा. हमारा सुझाव है कि आप passkey_user_id को प्राथमिक कुंजी के तौर पर सेट न करें. सिस्टम में प्राथमिक कुंजियां, व्यक्तिगत पहचान से जुड़ी जानकारी (पीआईआई) बन जाती हैं, क्योंकि इनका बड़े पैमाने पर इस्तेमाल किया जाता है.
  • सार्वजनिक पासकोड क्रेडेंशियल टेबल:
    • id: क्रेडेंशियल आईडी. इसका इस्तेमाल, सार्वजनिक पासकोड क्रेडेंशियल टेबल के लिए प्राथमिक कुंजी के तौर पर करें.
    • public_key: क्रेडेंशियल का सार्वजनिक पासकोड.
    • passkey_user_id: उपयोगकर्ताओं की टेबल से लिंक करने के लिए, इसे फ़ॉरेन की के तौर पर इस्तेमाल करें.
    • backed_up: अगर पासकी की सेवा देने वाली कंपनी पासकी को सिंक करती है, तो उसका बैकअप लिया जाता है. बैकअप की स्थिति को सेव करना तब फ़ायदेमंद होता है, जब आपको आने वाले समय में उन उपयोगकर्ताओं के लिए पासवर्ड की सुविधा को बंद करना हो जिनके पास backed_up पासकी हैं. पासकी का बैकअप लिया गया है या नहीं, यह देखने के लिए authenticatorData में बीई फ़्लैग की जांच करें. इसके अलावा, FIDO सर्वर-साइड लाइब्रेरी की सुविधा का इस्तेमाल करके भी यह जानकारी देखी जा सकती है. आम तौर पर, यह सुविधा आपको आसानी से यह जानकारी देने के लिए उपलब्ध होती है. बैकअप लेने की ज़रूरी शर्तें सेव करने से, उपयोगकर्ता की संभावित क्वेरी का जवाब देने में मदद मिल सकती है.
    • name: क्रेडेंशियल के लिए डिसप्ले नेम. इससे उपयोगकर्ता, क्रेडेंशियल को अपनी पसंद के मुताबिक नाम दे सकते हैं. हालांकि, ऐसा करना ज़रूरी नहीं है.
    • transports: transports का कलेक्शन. ट्रांसपोर्ट को सेव करने से, उपयोगकर्ता की पहचान की पुष्टि करने में मदद मिलती है. ट्रांसपोर्ट उपलब्ध होने पर, ब्राउज़र उसके हिसाब से काम कर सकता है. साथ ही, वह ऐसा यूज़र इंटरफ़ेस (यूआई) दिखा सकता है जो पासकी की सेवा देने वाली कंपनी के क्लाइंट के साथ कम्यूनिकेट करने के लिए इस्तेमाल किए जाने वाले ट्रांसपोर्ट से मैच करता हो. खास तौर पर, फिर से पुष्टि करने के उन मामलों में जहां allowCredentials खाली न हो.

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

कोड का उदाहरण: क्रेडेंशियल सेव करना

हम अपने उदाहरणों में SimpleWebAuthn लाइब्रेरी का इस्तेमाल कर रहे हैं. यहां हम रजिस्ट्रेशन के जवाब की पुष्टि करने की ज़िम्मेदारी, इसके verifyRegistrationResponse फ़ंक्शन को सौंपते हैं.

import { isoBase64URL } from '@simplewebauthn/server/helpers';


router.post('/registerResponse', csrfCheck, sessionCheck, async (req, res) => {
  const expectedChallenge = req.session.challenge;
  const expectedOrigin = getOrigin(req.get('User-Agent'));
  const expectedRPID = process.env.HOSTNAME;
  const response = req.body;
  // This sample code is for registering a passkey for an existing,
  // signed-in user

  // Ensure you nest verification function calls in try/catch blocks.
  // If something fails, throw an error with a descriptive error message.
  // Return that message with an appropriate error code to the client.
  try {
    // Verify the credential
    const { verified, registrationInfo } = await verifyRegistrationResponse({
      response,
      expectedChallenge,
      expectedOrigin,
      expectedRPID,
      requireUserVerification: false,
    });

    if (!verified) {
      throw new Error('Verification failed.');
    }

    const {
      aaguid,
      credential,
      credentialBackedUp
    } = registrationInfo;

    // Name the credential based on AAGUID
    const name =
      aaguid === undefined ||
      aaguid === '00000000-0000-0000-0000-000000000000' ?
        req.useragent?.platform : aaguids[aaguid]?.name;

    const base64PublicKey = isoBase64URL.fromBuffer(credential.publicKey);

    // Existing, signed-in user
    const { user } = res.locals;

    // Save the credential
    await Credentials.update({
      id: credential.id,
      passkey_user_id: user.passkey_user_id,
      publicKey: base64PublicKey,
      name,
      aaguid,
      transports: credential.transports || response.response.transports,
      backed_up: credentialBackedUp,
      registered_at: new Date().getTime()
    });

    // Kill the challenge for this session
    delete req.session.challenge;

    return res.json(user);
  } catch (e) {
    delete req.session.challenge;

    console.error(e);
    return res.status(400).send({ error: e.message });
  }
});

परिशिष्ट: AuthenticatorAttestationResponse

AuthenticatorAttestationResponse में दो अहम ऑब्जेक्ट होते हैं:

  • response.clientDataJSON, क्लाइंट डेटा का JSON वर्शन है. वेब पर यह डेटा, ब्राउज़र से दिखता है. इसमें आरपी का ऑरिजिन, चैलेंज, और androidPackageName शामिल होता है. ऐसा तब होता है, जब क्लाइंट कोई Android ऐप्लिकेशन हो. आरपी के तौर पर, clientDataJSON को पढ़ने से आपको उस जानकारी का ऐक्सेस मिलता है जो ब्राउज़र ने create के अनुरोध के समय देखी थी.
  • response.attestationObjectमें दो तरह की जानकारी होती है:
    • attestationStatement, जो तब तक काम का नहीं है, जब तक अटेस्टेशन का इस्तेमाल नहीं किया जाता.
    • authenticatorData, पासकी की सेवा देने वाली कंपनी को दिखने वाला डेटा है. authenticatorData को पढ़ने से, आरपी के तौर पर आपको उस डेटा का ऐक्सेस मिलता है जो पासकी की सेवा देने वाली कंपनी को दिखता है और create अनुरोध के समय मिलता है.

authenticatorDataमें, नई पासकी से जुड़े सार्वजनिक पासकोड क्रेडेंशियल के बारे में ज़रूरी जानकारी शामिल होती है:

भले ही, authenticatorData को attestationObject में नेस्ट किया गया हो, लेकिन इसमें मौजूद जानकारी पासकी को लागू करने के लिए ज़रूरी है. भले ही, आपने अटेस्टेशन का इस्तेमाल किया हो या नहीं. authenticatorData को एन्कोड किया गया है. इसमें ऐसे फ़ील्ड शामिल हैं जिन्हें बाइनरी फ़ॉर्मैट में एन्कोड किया गया है. आम तौर पर, आपकी सर्वर-साइड लाइब्रेरी, पार्स और डिकोड करने की प्रोसेस को मैनेज करेगी. अगर सर्वर-साइड लाइब्रेरी का इस्तेमाल नहीं किया जा रहा है, तो getAuthenticatorData() क्लाइंट-साइड का इस्तेमाल करें. इससे आपको सर्वर-साइड पर पार्स करने और डिकोड करने की ज़रूरत नहीं पड़ेगी.

अपेंडिक्स: रजिस्ट्रेशन के जवाब की पुष्टि करना

रजिस्ट्रेशन के जवाब की पुष्टि करने के लिए, ये जांच की जाती हैं:

  • पक्का करें कि आरपी आईडी आपकी साइट से मैच करता हो.
  • पक्का करें कि अनुरोध का ऑरिजिन, आपकी साइट (मुख्य साइट का यूआरएल, Android ऐप्लिकेशन) के लिए सही ऑरिजिन हो.
  • अगर आपको उपयोगकर्ता की पुष्टि करनी है, तो पक्का करें कि उपयोगकर्ता की पुष्टि करने वाला फ़्लैग authenticatorData.uv, true पर सेट हो.
  • आम तौर पर, उपयोगकर्ता की मौजूदगी का फ़्लैग authenticatorData.up, true पर सेट होता है. हालांकि, अगर क्रेडेंशियल शर्त के साथ बनाया गया है, तो इसके false पर सेट होने की उम्मीद होती है.
  • देखें कि क्लाइंट ने आपके दिए गए चैलेंज को पूरा किया है या नहीं. अगर अटेस्टेशन का इस्तेमाल नहीं किया जाता है, तो यह जांच ज़रूरी नहीं है. हालांकि, इस जांच को लागू करना सबसे सही तरीका है: इससे यह पक्का होता है कि अगर आने वाले समय में आपको पुष्टि करने की सुविधा का इस्तेमाल करना है, तो आपका कोड तैयार है.
  • पक्का करें कि क्रेडेंशियल आईडी, अब तक किसी भी उपयोगकर्ता के लिए रजिस्टर न किया गया हो.
  • पुष्टि करें कि पासकी की सेवा देने वाली कंपनी ने क्रेडेंशियल बनाने के लिए, जिस एल्गोरिदम का इस्तेमाल किया है वह आपकी सूची में शामिल हो. यह सूची, publicKeyCredentialCreationOptions.pubKeyCredParams के हर alg फ़ील्ड में मौजूद होती है. आम तौर पर, इसे आपके सर्वर-साइड लाइब्रेरी में तय किया जाता है और यह आपको नहीं दिखती. इससे यह पक्का होता है कि उपयोगकर्ता सिर्फ़ उन एल्गोरिदम के साथ रजिस्टर कर सकते हैं जिन्हें आपने अनुमति दी है.

ज़्यादा जानने के लिए, verifyRegistrationResponse के लिए SimpleWebAuthn का सोर्स कोड देखें या स्पेसिफ़िकेशन में पुष्टि करने के तरीकों की पूरी सूची देखें.

अगला सेक्शन

सर्वर-साइड पासकी ऑथेंटिकेशन