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

खास जानकारी

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

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

  • पासकी की मदद से पुष्टि करने के लिए ज़रूरी चुनौती और अन्य विकल्प तय करें. उसे क्लाइंट को भेजें, ताकि आप उसे पासकी की पुष्टि करने वाले कॉल (वेब पर navigator.credentials.get) में पास कर सकें. जब उपयोगकर्ता, पासकी से पुष्टि करने की पुष्टि करता है, तब पासकी की पुष्टि करने वाला कॉल हल हो जाता है और यह क्रेडेंशियल (PublicKeyCredential) दिखाता है. क्रेडेंशियल में पुष्टि करने का दावा शामिल होता है.
  • पुष्टि करने के दावे की पुष्टि करें.
  • अगर पुष्टि करने का दावा मान्य है, तो उपयोगकर्ता की पुष्टि करें.

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

चैलेंज बनाएं

व्यावहारिक तौर पर, चैलेंज रैंडम बाइट का एक कलेक्शन होता है, जिसे ArrayBuffer ऑब्जेक्ट के तौर पर दिखाया जाता है.

// Example challenge, base64URL-encoded
weMLPOSx1VfSnMV6uPwDKbjGdKRMaUDGxeDEUTT5VN8

यह पक्का करने के लिए कि चैलेंज का मकसद पूरा हो, आपको:

  1. पक्का करें कि एक ही चैलेंज का इस्तेमाल कभी भी एक से ज़्यादा बार न किया जाए. साइन-इन करने की हर कोशिश पर, एक नया चैलेंज जनरेट करें. साइन इन करने की हर कोशिश के बाद, इस चैलेंज को खारिज कर दें. भले ही, यह चैलेंज पूरा हो गया हो या असफल. साथ ही, तय समय के बाद चैलेंज को खारिज कर दें. एक जवाब को एक से ज़्यादा बार स्वीकार न करें.
  2. पक्का करें कि यह चैलेंज क्रिप्टोग्राफ़िक तौर पर सुरक्षित है. किसी चैलेंज का अनुमान लगाना व्यावहारिक तौर पर नामुमकिन होना चाहिए. क्रिप्टोग्राफ़िक तौर पर सुरक्षित चैलेंज सर्वर-साइड बनाने के लिए, बेहतर है कि आप FIDO सर्वर साइड की भरोसेमंद लाइब्रेरी का इस्तेमाल करें. अगर इसके बजाय खुद की चुनौतियां बनाई जाती हैं, तो अपने टेक्नोलॉजी स्टैक में पहले से मौजूद क्रिप्टोग्राफ़िक सुविधा का इस्तेमाल करें या ऐसी लाइब्रेरी खोजें जिन्हें क्रिप्टोग्राफ़िक इस्तेमाल के लिए डिज़ाइन किया गया है. उदाहरण के लिए, Node.js में iso-crypto या Python के सीक्रेट. खास जानकारी के मुताबिक, चैलेंज को कम से कम 16 बाइट का होना चाहिए, ताकि उसे सुरक्षित माना जा सके.

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

क्रेडेंशियल के लिए अनुरोध करने के विकल्प बनाएं

publicKeyCredentialRequestOptions ऑब्जेक्ट के तौर पर, क्रेडेंशियल अनुरोध के विकल्प बनाएं.

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

publicKeyCredentialRequestOptions में पासकी की पुष्टि करने के लिए सभी ज़रूरी जानकारी होनी चाहिए. इस जानकारी को अपनी FIDO सर्वर साइड लाइब्रेरी में मौजूद फ़ंक्शन को भेजें, जो publicKeyCredentialRequestOptions ऑब्जेक्ट बनाने के लिए ज़िम्मेदार है.

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

  • rpId: आपको क्रेडेंशियल किस आरपी आईडी से जोड़ना है, जैसे कि example.com. पुष्टि सिर्फ़ तब हो पाएगी, जब आपने यहां जो आरपी आईडी दिया हो वह क्रेडेंशियल से जुड़े आरपी आईडी से मेल खाता हो. आरपी आईडी की जानकारी अपने-आप भरने के लिए, उसी वैल्यू का इस्तेमाल करें जो आपने क्रेडेंशियल रजिस्ट्रेशन के दौरान publicKeyCredentialCreationOptions में सेट किया गया आरपी आईडी है.
  • challenge: यह ऐसा डेटा होता है जिसे पासकी की सेवा देने वाली कंपनी, यह साबित करने के लिए साइन करेगी कि पुष्टि करने का अनुरोध करते समय, उपयोगकर्ता के पास पासकी मौजूद होती है. चुनौती बनाएं में दी गई जानकारी देखें.
  • allowCredentials: पुष्टि करने के लिए इस्तेमाल किए जा सकने वाले क्रेडेंशियल का कलेक्शन. खाली कलेक्शन पास करें, ताकि उपयोगकर्ता ब्राउज़र की दिखाई गई सूची में से उपलब्ध पासकी चुन सके. ज़्यादा जानकारी के लिए, आरपी सर्वर से कोई चैलेंज फ़ेच करना और खोजे जा सकने वाले क्रेडेंशियल की पूरी जानकारी देखें.
  • userVerification: इससे पता चलता है कि डिवाइस के स्क्रीन लॉक का इस्तेमाल करके, उपयोगकर्ता की पुष्टि करना "ज़रूरी", "पसंदीदा" है या नहीं या "नाराज़गी". आरपी सर्वर से कोई चैलेंज फ़ेच करने का तरीका देखें.
  • timeout: पुष्टि करने की प्रक्रिया पूरी करने में उपयोगकर्ता को कितना समय (मिलीसेकंड में) लग सकता है. यह काफ़ी उदार और challenge के लाइफ़टाइम से छोटा होना चाहिए. हमारा सुझाव है कि डिफ़ॉल्ट वैल्यू 5 मिनट सेट करें. हालांकि, इसे 10 मिनट तक बढ़ाया जा सकता है. हालांकि, यह वैल्यू अब भी सुझाई गई रेंज में है. अगर आपको लगता है कि उपयोगकर्ता हाइब्रिड वर्कफ़्लो का इस्तेमाल करेंगे, तो लंबी टाइम आउट की सुविधा का इस्तेमाल करना सही रहेगा. इसमें आम तौर पर थोड़ा ज़्यादा समय लगता है. अगर ऑपरेशन का समय खत्म हो जाता है, तो एक NotAllowedError फेंका जाएगा.

publicKeyCredentialRequestOptions बनाने के बाद, उसे क्लाइंट को भेजें.

सर्वर से भेजा गया PublicKeyCredentialCreationOptions
सर्वर से भेजे गए विकल्प. challenge को डिकोड करने की प्रोसेस क्लाइंट-साइड होती है.

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

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

import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  verifyAuthenticationResponse
} from '@simplewebauthn/server';

router.post('/signinRequest', csrfCheck, async (req, res) => {

  // Ensure you nest 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 {
    // Use the generateAuthenticationOptions function from SimpleWebAuthn
    const options = await generateAuthenticationOptions({
      rpID: process.env.HOSTNAME,
      allowCredentials: [],
    });
    // Save the challenge in the user session
    req.session.challenge = options.challenge;

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

उपयोगकर्ता की पुष्टि करके, साइन इन करें

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

PublicKeyक्रेडेंशियल ऑब्जेक्ट सर्वर से भेजा गया
navigator.credentials.get, PublicKeyCredential दिखाता है.

response, एक AuthenticatorAssertionResponse है. इससे पता चलता है कि पासकी की सेवा देने वाली कंपनी, क्लाइंट के निर्देश पर जवाब देती है. इसमें यह बताया जाता है कि आरपी पर पासकी की मदद से पुष्टि करने और आज़माने के लिए क्या ज़रूरी है. इसमें ये चीज़ें शामिल हैं:

  • response.authenticatorDataऔरresponse.clientDataJSON, जैसे कि पासकी रजिस्ट्रेशन के चरण में.
  • response.signature, जिसमें इन वैल्यू के ऊपर हस्ताक्षर मौजूद है.

PublicKeyCredential ऑब्जेक्ट को सर्वर पर भेजें.

सर्वर पर, ये काम करें:

डेटाबेस स्कीमा
सुझाया गया डेटाबेस स्कीमा. इस डिज़ाइन के बारे में ज़्यादा जानने के लिए, सर्वर साइड पासकी रजिस्ट्रेशन पर जाएं.
  • दावे की पुष्टि और उपयोगकर्ता की पुष्टि करने के लिए, ज़रूरी जानकारी इकट्ठा करें:
    • पुष्टि करने के विकल्प जनरेट करने पर, सेशन में सेव की गई चुनौती का पूरा डेटा पाएं.
    • अनुमानित ऑरिजिन और आरपी आईडी पाएं.
    • अपने डेटाबेस में देखें कि उपयोगकर्ता कौन है. क्रेडेंशियल खोजे जा सकने वाले क्रेडेंशियल के मामले में, आपको यह नहीं पता होता कि पुष्टि करने का अनुरोध करने वाला उपयोगकर्ता कौन है. इसका पता लगाने के लिए आपके पास दो विकल्प हैं:
      • पहला विकल्प: PublicKeyCredential ऑब्जेक्ट में, response.userHandle का इस्तेमाल करें. उपयोगकर्ता टेबल में, userHandle से मेल खाने वाला passkey_user_id खोजें.
      • दूसरा विकल्प: PublicKeyCredential ऑब्जेक्ट में मौजूद क्रेडेंशियल id का इस्तेमाल करें. सार्वजनिक कुंजी के क्रेडेंशियल टेबल में, वह क्रेडेंशियल id खोजें जो PublicKeyCredential ऑब्जेक्ट में मौजूद क्रेडेंशियल id से मेल खाता हो. इसके बाद, अपने उपयोगकर्ता टेबल में विदेशी कुंजी passkey_user_id का इस्तेमाल करके, संबंधित उपयोगकर्ता खोजें.
    • अपने डेटाबेस में सार्वजनिक पासकोड के क्रेडेंशियल की वह जानकारी ढूंढें जो आपको मिले, पुष्टि करने के दावे से मिलती-जुलती है. ऐसा करने के लिए, Public Key क्रेडेंशियल टेबल में, ऐसा क्रेडेंशियल id खोजें जो PublicKeyCredential ऑब्जेक्ट में मौजूद क्रेडेंशियल id से मेल खाता हो.
  • पुष्टि करने के दावे की पुष्टि करें. पुष्टि करने के इस चरण को अपनी FIDO सर्वर साइड लाइब्रेरी पर दें. आम तौर पर, इस चरण में इस काम के लिए यूटिलिटी फ़ंक्शन उपलब्ध होता है. SimpleWebAuthn ऑफ़र, उदाहरण के लिए, verifyAuthenticationResponse. जानें कि हुड में क्या हो रहा है. इसके लिए, अपेंडिक्स: पुष्टि करने के जवाब की पुष्टि करना सेक्शन पर जाएं.

  • रीप्ले से होने वाले हमलों को रोकने के लिए, चैलेंज को मिटाएं, चाहे पुष्टि पूरी होती हो या नहीं.

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

कोड का उदाहरण: उपयोगकर्ता की पुष्टि करें और साइन इन करें

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

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

router.post('/signinResponse', csrfCheck, async (req, res) => {
  const response = req.body;
  const expectedChallenge = req.session.challenge;
  const expectedOrigin = getOrigin(req.get('User-Agent'));
  const expectedRPID = process.env.HOSTNAME;

  // 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 {
    // Find the credential stored to the database by the credential ID
    const cred = Credentials.findById(response.id);
    if (!cred) {
      throw new Error('Credential not found.');
    }
    // Find the user - Here alternatively we could look up the user directly
    // in the Users table via userHandle
    const user = Users.findByPasskeyUserId(cred.passkey_user_id);
    if (!user) {
      throw new Error('User not found.');
    }
    // Base64URL decode some values
    const authenticator = {
      credentialPublicKey: isoBase64URL.toBuffer(cred.publicKey),
      credentialID: isoBase64URL.toBuffer(cred.id),
      transports: cred.transports,
    };

    // Verify the credential
    const { verified, authenticationInfo } = await verifyAuthenticationResponse({
      response,
      expectedChallenge,
      expectedOrigin,
      expectedRPID,
      authenticator,
      requireUserVerification: false,
    });

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

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

    req.session.username = user.username;
    req.session['signed-in'] = 'yes';

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

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

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

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

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

इन चरणों के बारे में ज़्यादा जानने के लिए, SimpleWebAuthn के verifyAuthenticationResponse के लिए सोर्स कोड देखें या खास जानकारी में पुष्टि करने की पूरी सूची देखें.