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

खास जानकारी

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

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

  • पासकी से पुष्टि करने के लिए ज़रूरी चैलेंज और अन्य विकल्पों के बारे में बताएं. उन्हें क्लाइंट को भेजें, ताकि आप उन्हें पासकी की पुष्टि करने वाले कॉल (वेब पर 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 के लाइफ़टाइम से कम होनी चाहिए. डिफ़ॉल्ट तौर पर, सुझाई गई वैल्यू 5 मिनट होती है. हालांकि, इसे 10 मिनट तक बढ़ाया जा सकता है. हालांकि, यह वैल्यू सुझाई गई सीमा में ही रहेगी. अगर आपको लगता है कि उपयोगकर्ता हाइब्रिड वर्कफ़्लो का इस्तेमाल करेंगे, तो टाइम आउट में ज़्यादा समय लग सकता है. आम तौर पर, इसमें ज़्यादा समय लगता है. अगर कार्रवाई का समय खत्म हो जाता है, तो NotAllowedError चला जाएगा.

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

सर्वर से भेजे गए PublicKeyPrivacyCreationOptions
सर्वर से भेजे गए विकल्प. 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 का इस्तेमाल करने वाले उपयोगकर्ता को खोजें.
    • अपने डेटाबेस में, क्रेडेंशियल की उस सार्वजनिक जानकारी को ढूंढें जो आपको मिले पुष्टि के दावे से मेल खाती है. ऐसा करने के लिए, सार्वजनिक कुंजी के क्रेडेंशियल टेबल में, क्रेडेंशियल 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 के लिए सोर्स कोड देखें या जानकारी में पुष्टि की पूरी सूची देखें.