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

खास जानकारी

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

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

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

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

चैलेंज बनाएं

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

// Example challenge, base64URL-encoded
weMLPOSx1VfSnMV6uPwDKbjGdKRMaUDGxeDEUTT5VN8

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

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

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

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

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

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

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

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

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

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

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

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

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

import {
  generateAuthenticationOptions,
} 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 ऑब्जेक्ट दिखाता है.

सर्वर से भेजा गया PublicKeyCredential ऑब्जेक्ट
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 का इस्तेमाल करके, उससे जुड़े उपयोगकर्ता को ढूंढें.
    • अपने डेटाबेस में, सार्वजनिक पासकोड क्रेडेंशियल की वह जानकारी ढूंढें जो आपको मिले पुष्टि के दावे से मेल खाती हो. इसके लिए, सार्वजनिक पासकोड क्रेडेंशियल टेबल में, id क्रेडेंशियल ढूंढें. यह क्रेडेंशियल, PublicKeyCredential ऑब्जेक्ट में मौजूद id क्रेडेंशियल से मैच होना चाहिए.
  • पुष्टि करने के दावे की पुष्टि करें. पुष्टि करने के इस चरण को अपनी FIDO सर्वर-साइड लाइब्रेरी को सौंपें. आम तौर पर, यह लाइब्रेरी इस काम के लिए एक यूटिलिटी फ़ंक्शन उपलब्ध कराती है. उदाहरण के लिए, SimpleWebAuthn verifyAuthenticationResponse उपलब्ध कराता है. परिशिष्ट: पुष्टि करने के जवाब की पुष्टि में जानें कि बैकग्राउंड में क्या हो रहा है.

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

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

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

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

import {
  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.');
    }
    // Prepare credential information for verification
    const credential = {
      id: cred.id,
      publicKey: isoBase64URL.toBuffer(cred.publicKey),
      transports: cred.transports,
    };

    // Verify the credential
    const { verified, authenticationInfo } = await verifyAuthenticationResponse({
      response,
      expectedChallenge,
      expectedOrigin,
      expectedRPID,
      credential,
      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 के लिए सोर्स कोड देखें या स्पेसिफ़िकेशन में पुष्टि करने के तरीकों की पूरी सूची देखें.