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

खास जानकारी

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

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

  • पासकी बनाने के विकल्प तय करें. इन्हें क्लाइंट को भेजें, ताकि पासकी बनाने के लिए किए गए कॉल में इनका इस्तेमाल किया जा सके: वेब पर 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: यह एक रैंडम और यूनीक स्ट्रिंग होती है. इसे खाता बनाते समय जनरेट किया जाता है. यह हमेशा के लिए होना चाहिए. उपयोगकर्ता नाम की तरह, इसमें बदलाव नहीं किया जा सकता. User-ID से किसी खाते की पहचान होती है. हालांकि, इसमें व्यक्तिगत पहचान से जुड़ी कोई जानकारी (पीआईआई) शामिल नहीं होनी चाहिए. आपके सिस्टम में शायद पहले से ही कोई उपयोगकर्ता आईडी मौजूद हो. हालांकि, अगर ज़रूरत हो, तो पासकी के लिए एक अलग उपयोगकर्ता आईडी बनाएं, ताकि इसमें कोई भी निजी पहचान से जुड़ी जानकारी न हो.
  • excludeCredentials: मौजूदा क्रेडेंशियल के आईडी की सूची. इससे पासकी प्रोवाइडर से पासकी को डुप्लीकेट होने से रोका जा सकता है. इस फ़ील्ड में जानकारी भरने के लिए, अपने डेटाबेस में इस उपयोगकर्ता के मौजूदा क्रेडेंशियल देखें. अगर पासकी पहले से मौजूद है, तो नई पासकी बनाने से रोकना पर जाकर, ज़्यादा जानकारी देखें.
  • challenge: क्रेडेंशियल रजिस्टर करने के लिए, चुनौती का कोई मतलब नहीं है. हालांकि, अगर आपको पासकी की सुविधा देने वाली कंपनी की पहचान और उसके भेजे गए डेटा की पुष्टि करने के लिए, ज़्यादा बेहतर तकनीक यानी कि अटेस्टेशन का इस्तेमाल करना है, तो चुनौती का इस्तेमाल किया जा सकता है. हालांकि, अगर आपने पुष्टि करने की सुविधा का इस्तेमाल नहीं किया है, तब भी चुनौती वाला फ़ील्ड भरना ज़रूरी है. सर्वर-साइड पासकी ऑथेंटिकेशन में, पुष्टि करने के लिए सुरक्षित चैलेंज बनाने के निर्देश दिए गए हैं.

कोडिंग और डिकोडिंग

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

PublicKeyCredentialCreationOptions में ऐसे फ़ील्ड शामिल हैं जो ArrayBuffer हैं. इसलिए, JSON.stringify() के साथ काम नहीं करते. इसका मतलब है कि फ़िलहाल, एचटीटीपीएस पर PublicKeyCredentialCreationOptions डिलीवर करने के लिए, कुछ फ़ील्ड को सर्वर पर base64URL का इस्तेमाल करके मैन्युअल तरीके से एन्कोड करना होगा. इसके बाद, उन्हें क्लाइंट पर डिकोड करना होगा.

  • सर्वर पर, एन्कोडिंग और डिकोडिंग की प्रोसेस को आम तौर पर आपकी FIDO सर्वर-साइड लाइब्रेरी मैनेज करती है.
  • क्लाइंट पर, फ़िलहाल एन्कोडिंग और डिकोडिंग मैन्युअल तरीके से करनी होगी. आने वाले समय में, यह काम और भी आसान हो जाएगा: विकल्पों को JSON के तौर पर PublicKeyCredentialCreationOptions में बदलने का तरीका उपलब्ध होगा. Chrome में लागू करने का स्टेटस देखें.

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

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

import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  verifyAuthenticationResponse
} from '@simplewebauthn/server';
import { isoBase64URL } 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: isoBase64URL.toBuffer(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: user.id,
      userName: user.username,
      userDisplayName: user.displayName || '',
      attestationType: 'none',
      excludeCredentials,
      authenticatorSelection: {
        authenticatorAttachment: 'platform',
        requireResidentKey: true
      },
    });

    // 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 के ज़रिए सिंक की गई पासकी.

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

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

  • Users टेबल:
    • user_id: प्राइमरी यूज़र आईडी. यह कुकी, उपयोगकर्ता के लिए रैंडम, यूनीक, और परमानेंट आईडी सेट करती है. इसका इस्तेमाल, अपनी Users टेबल के लिए प्राइमरी कुंजी के तौर पर करें.
    • username. उपयोगकर्ता की ओर से तय किया गया यूज़रनेम. इसमें बदलाव किया जा सकता है.
    • passkey_user_id: पासकी के लिए, व्यक्तिगत पहचान से जुड़ी जानकारी के बिना उपयोगकर्ता का आईडी. इसे user.id के तौर पर दिखाया जाता है. यह आपके रजिस्ट्रेशन के विकल्पों में दिखता है. जब उपयोगकर्ता बाद में पुष्टि करने की कोशिश करेगा, तो पुष्टि करने वाला ऐप्लिकेशन, passkey_user_id में पुष्टि करने के जवाब में इसे उपलब्ध कराएगा.userHandle हमारा सुझाव है कि आप passkey_user_id को मुख्य कुंजी के तौर पर सेट न करें. प्राइमरी कुंजियां, सिस्टम में PII बन जाती हैं, क्योंकि इनका इस्तेमाल बड़े पैमाने पर किया जाता है.
  • सार्वजनिक पासकोड क्रेडेंशियल टेबल:
    • id: क्रेडेंशियल आईडी. इसका इस्तेमाल, Public key credentials टेबल के लिए प्राइमरी कुंजी के तौर पर करें.
    • public_key: क्रेडेंशियल का सार्वजनिक पासकोड.
    • passkey_user_id: इसका इस्तेमाल, Users टेबल से लिंक करने के लिए फ़ॉरेन की के तौर पर करें.
    • backed_up: पासकी को बैक अप तब किया जाता है, जब पासकी की सुविधा देने वाली कंपनी उसे सिंक करती है. अगर आपको backed_up पासकी रखने वाले उपयोगकर्ताओं के लिए, आने वाले समय में पासवर्ड का इस्तेमाल बंद करना है, तो बैकअप की स्थिति को सेव करना फ़ायदेमंद होता है. यह देखने के लिए कि पासकी का बैक अप लिया गया है या नहीं, authenticatorData में BE फ़्लैग की जांच करें. इसके अलावा, 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,
      credentialPublicKey,
      credentialID,
      credentialBackedUp
    } = registrationInfo;

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

    const base64CredentialID = isoBase64URL.fromBuffer(credentialID);
    const base64PublicKey = isoBase64URL.fromBuffer(credentialPublicKey);

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

    // Save the credential
    await Credentials.update({
      id: base64CredentialID,
      passkey_user_id: user.passkey_user_id,
      publicKey: base64PublicKey,
      name,
      aaguid,
      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 शामिल होता है. androidPackageName सिर्फ़ तब शामिल होता है, जब क्लाइंट कोई Android ऐप्लिकेशन हो. आरपी के तौर पर, clientDataJSON को पढ़ने से आपको उस जानकारी का ऐक्सेस मिलता है जो ब्राउज़र ने create अनुरोध के समय देखी थी.
  • response.attestationObjectमें दो तरह की जानकारी होती है:
    • attestationStatement, जो तब तक काम का नहीं है, जब तक कि पुष्टि करने की सुविधा का इस्तेमाल न किया जाए.
    • authenticatorData पासकी की सुविधा देने वाली कंपनी को दिखने वाला डेटा है. आरपी के तौर पर, authenticatorDataसे आपको उस डेटा का ऐक्सेस मिलता है जो पासकी सेवा देने वाली कंपनी को दिखता है. साथ ही, create के अनुरोध के समय यह डेटा वापस मिल जाता है.

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

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

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

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

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

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

अगला

सर्वर साइड से पासकी की पुष्टि करना