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

इस गाइड में, खोजे जा सकने वाले क्रेडेंशियल का इस्तेमाल करके, लौटने वाले उपयोगकर्ता की पुष्टि करने का तरीका बताया गया है. इसके लिए, बटन ट्रिगर और ब्राउज़र में ऑटोमैटिक भरने की सुविधा के सुझावों (कंडिशनल यूज़र इंटरफ़ेस (यूआई)) का इस्तेमाल किया जाता है.

सर्वर-साइड

विकल्प जनरेट करने की सुविधा

ऐसा एंडपॉइंट बनाएं जो स्टैंडर्ड के मुताबिक, जांची-परखी लाइब्रेरी का इस्तेमाल करके WebAuthn अनुरोध पैरामीटर जनरेट करता हो.

  1. पहले से तय आरपी आईडी का इस्तेमाल करना: पहले से तय सही आरपी आईडी को कॉन्स्टेंट स्ट्रिंग के तौर पर इस्तेमाल करें.
  2. चैलेंज जनरेट करना: हाई-एंट्रॉपी वाला, क्रिप्टोग्राफ़िक तौर पर सुरक्षित रैंडम बफ़र जनरेट करें. इसे उपयोगकर्ता के सेशन में सुरक्षित तरीके से सेव करें और Base64URL के तौर पर कोड में बदलें.
  3. खोजे जा सकने वाले क्रेडेंशियल की मैपिंग: allowCredentials के लिए, खाली कलेक्शन [] तय करें. यह खोजे जा सकने वाले क्रेडेंशियल का अनुरोध करता है. इसका मतलब है कि उपयोगकर्ता को पहले अपना उपयोगकर्ता नाम डालने की ज़रूरत नहीं है. पासकी की सेवा देने वाली कंपनी, उपलब्ध खाते दिखाएगी.
  4. उपयोगकर्ता की पुष्टि का लेवल: userVerification: "preferred" पर सेट करें. अगर कॉर्पोरेट की अनुपालन नीतियों के तहत साफ़ तौर पर ज़रूरी हो, तो "required" पर सेट करें.
    • अनुरोध किए गए userVerification कंस्ट्रेंट लेवल को, क्वेरी स्ट्रिंग के ज़रिए क्लाइंट से वापस भेजने के बजाय, विकल्प एंडपॉइंट पर सर्वर सेशन रिकॉर्ड में सेव किया जाना चाहिए. इससे पुष्टि करने वाले एंडपॉइंट को, मैच करने से जुड़ी सख्त पाबंदियों को सुरक्षित तरीके से लागू करने में मदद मिलती है. साथ ही, क्लाइंट की ओर से छेड़छाड़ का जोखिम भी नहीं रहता.
// Options generation example (discoverable flow)
const options = {
  challenge: serverGeneratedBase64UrlChallenge, // High-entropy random challenge stored in session
  rpId: "example.com",
  allowCredentials: [], // Request discoverable passkeys
  userVerification: "preferred",
};

// Persist expected UV level to user session
req.session.expectedUserVerification = "preferred";

पुष्टि करने वाला एंडपॉइंट

उपयोगकर्ता की पुष्टि करने के लिए, क्लाइंट से मिले दावे की सुरक्षित तरीके से पुष्टि करें:

  1. सेशन चैलेंज की पुष्टि करना: क्लाइंट के जवाब और सेशन में सेव किए गए चैलेंज के बीच, मैचिंग को सख्ती से लागू करें.
  2. यूवी से जुड़ी प्राथमिकताएं लागू करें:
    • अगर सेशन के expectedUserVerification ने "preferred" का अनुरोध किया है, तो बिना यूवी वाले ऑथेंटिकेटर (जैसे, ऑथेंटिकेटर के स्क्रीन लॉक बंद होने पर) को अनुमति दें. इसके लिए, सर्वर साइड से की जाने वाली पुष्टि की लाइब्रेरी में requireUserVerification: false पास करें. अगर "required" का अनुरोध किया गया है, तो बायोमेट्रिक/पिन डालने की सुविधा को सख्ती से लागू करें.
  3. क्लीन सर्वर एरर 404: अगर क्लाइंट से मिला क्रेडेंशियल आईडी, डेटाबेस में नहीं मिलता है, तो एचटीटीपी 404 गड़बड़ी दिखाएं, ताकि क्लाइंट, Signal API को ट्रिगर कर सके.

क्लाइंट-साइड लॉजिक

एचटीएमएल फ़ॉर्म एनोटेशन

कंडीशनल यूज़र इंटरफ़ेस (यूआई) का नेटिव तौर पर फ़ायदा पाने के लिए, अपने उपयोगकर्ता नाम और पासवर्ड के इनपुट को एनोटेट करें. ऑटोकंप्लीट टोकन, WebAuthn के खास पैरामीटर को जोड़ते हैं. साथ ही, इनपुट पर फ़ोकस होने पर, ऑटोफ़ोकस की सुविधा ब्राउज़र में जानकारी अपने-आप भरने की सुविधा वाले पॉप-अप को तुरंत ट्रिगर करती है.

<!-- Autocomplete tokens must contain webauthn space-separated -->
<form id="signin-form">
  <input
    type="text"
    name="username"
    autocomplete="username webauthn"
    autofocus
    data-testid="username-field"
  />
  <input type="password" name="password" autocomplete="current-password" />
  <button type="submit">Sign in</button>
</form>

एक्सप्लिसिट बटन फ़्लो

जब कोई उपयोगकर्ता "पासकी से साइन इन करें" बटन पर क्लिक करे, तो पासकी से पुष्टि करने की सुविधा ट्रिगर करें. पासकी का प्रॉम्प्ट दिखाने से पहले, फ़ॉर्म को ऑटोमैटिक भरने की सुविधा (कंडीशनल गेट) के लिए चल रहे किसी भी कॉल को रद्द करें.

कंडिशनल मीडिएशन फ़्लो (फ़ॉर्म ऑटोमैटिक तरीके से भरना)

पेज लोड होने पर, फ़ॉर्म में जानकारी अपने-आप भरने के सुझाव देने की सुविधा चालू करें. इससे, साइन-इन फ़ील्ड पर फ़ोकस करने पर, उपयोगकर्ताओं को पासकी से पुष्टि करने की सुविधा नेटिव तौर पर मिलेगी:

  1. सुविधा का पता लगाना: पेज लोड होने पर PublicKeyCredential.getClientCapabilities() को कॉल करें. अगर conditionalGet उपलब्ध नहीं है, तो पासकी से साइन इन करने की सुविधा को स्किप करें.
  2. डिकोड करने के विकल्प: फ़ेच किए गए क्रेडेंशियल JSON ऑब्जेक्ट को PublicKeyCredential.parseRequestOptionsFromJSON() की मदद से डिकोड करें.
  3. कंडिशनल Get को शुरू करना: mediation: "conditional" के साथ navigator.credentials.get() को कॉल करें और AbortController सिग्नल पास करें. यह पासकी डायलॉग रेंडर किए बिना, जानकारी अपने-आप भरने की सुविधा को रजिस्टर करता है.
  4. Try/Catch Exception Segregation: navigator.credentials.get कॉल को try/catch ब्लॉक में रैप करें:
    • NotAllowedError: उपयोगकर्ता ने पासकी से लॉग इन करने का प्रॉम्प्ट रद्द कर दिया या प्रॉम्प्ट की समयसीमा खत्म हो गई.
    • AbortError: पुष्टि करने का अनुरोध, प्रोग्राम के हिसाब से रद्द कर दिया गया.
  5. Call Signal API: सर्वर की पुष्टि करने वाले fetch() कॉल को try/catch ब्लॉक में रैप करें:
    • उपयोगकर्ता को गड़बड़ी का मैसेज दिखाएं, ताकि उसे पता चल सके कि क्या गड़बड़ी हुई है.
    • signalUnknownCredential() को सिर्फ़ तब कॉल करें, जब सर्वर साफ़ तौर पर एचटीटीपी स्टेटस 404 (क्रेडेंशियल नहीं मिला) के साथ जवाब दे और उपयोगकर्ता की पुष्टि न हुई हो.
    • signalUnknownCredential() को पास किया गया credentialId पैरामीटर, Base64URL-एन्कोड की गई क्रेडेंशियल आईडी स्ट्रिंग (उदाहरण के लिए, encoded.id) होनी चाहिए, न कि रॉ ArrayBuffer ऑब्जेक्ट credential.rawId.
  6. जवाब को एन्कोड करना: क्रेडेंशियल AuthenticatorAssertionResponse को .toJSON() के साथ एन्कोड करें. इसके बाद, पुष्टि करने के लिए उसे सर्वर पर भेजें.
// optionsFetch and loginVerifyFetch are app-defined HTTP methods
import { optionsFetch, loginVerifyFetch } from "./api.js";

let autofillAbortController = new AbortController();

async function initializeConditionalAutofill() {
  // Feature detect Conditional Get autofill support
  const capabilities = await PublicKeyCredential.getClientCapabilities();
  if (capabilities.conditionalGet === true) {
    const loginOptionsJSON = await optionsFetch();
    const publicKey =
      PublicKeyCredential.parseRequestOptionsFromJSON(loginOptionsJSON);

    try {
      // Initiate Conditional UI form autofill suggestions
      const credential = await navigator.credentials.get({
        publicKey,
        signal: autofillAbortController.signal,
        mediation: "conditional",
      });

      // Segregated verification fetch
      const encoded = credential.toJSON();
      const response = await loginVerifyFetch(encoded);
      if (!response.ok && response.status === 404) {
        // Note: this code path runs pre-authentication, satisfying the unauth precondition
        if (PublicKeyCredential.signalUnknownCredential) {
          await PublicKeyCredential.signalUnknownCredential({
            rpId, // RP ID must match the one defined on the server
            credentialId: encoded.id,
          });
        }
      }
    } catch (err) {
      // Silently swallow expected client WebAuthn exceptions
      if (["NotAllowedError", "AbortError"].includes(err.name)) {
        return;
      }
      console.error("Unexpected conditional get error:", err);
    }
  }
}

async function triggerButtonAuthentication() {
  // Abort any pending Conditional Get call to prevent passkey prompt collisions
  autofillAbortController.abort();
  autofillAbortController = new AbortController(); // Reset controller for next triggers

  const loginOptionsJSON = await optionsFetch();
  const publicKey =
    PublicKeyCredential.parseRequestOptionsFromJSON(loginOptionsJSON);

  let credential;
  try {
    // Passkey explicit prompt trigger
    credential = await navigator.credentials.get({
      publicKey,
      signal: autofillAbortController.signal,
    });
  } catch (err) {
    if (err.name === "NotAllowedError") {
      console.log("User cancelled passkey login.");
    } else if (err.name === "AbortError") {
      console.log("The authentication operation was aborted.");
    }
    // Re-arm Conditional autofill Suggestions after cancelled explicit button prompts
    initializeConditionalAutofill();
    return; // Safe exit
  }

  // Segregated verification try/catch (HTTP 404 trigger)
  const encoded = credential.toJSON();
  try {
    const response = await loginVerifyFetch(encoded);
    if (!response.ok && response.status === 404) {
      // Note: this code path runs pre-authentication, satisfying the unauth precondition
      await PublicKeyCredential.signalUnknownCredential({
        rpId, // RP ID must match the one defined on the server
        credentialId: encoded.id, // Base64URL-encoded credential ID
      });
    }
  } catch (serverErr) {
    console.error("Verification request error:", serverErr);
  }
}

// Trigger Conditional Get on load
window.addEventListener("DOMContentLoaded", initializeConditionalAutofill);

फ़ॉलबैक रणनीतियां

पासकी की सुविधा का पता लगाने के लिए फ़ॉलबैक

हमेशा 'webauthn-polyfills' इंस्टॉल करें और उसे कॉन्टेक्स्ट में इंपोर्ट करें. ध्यान रखें कि जब तक PublicKeyCredential का इस्तेमाल किया जा सकता है, तब तक PublicKeyCredential.getClientCapabilities का भी इस्तेमाल किया जा सकता है.

import 'webauthn-polyfills';

Signal API Synchronization Fallback

WebAuthn Signal API (webauthn-signals) एक प्रोग्रेसिव ऑप्टिमाइज़ेशन है. इसका इस्तेमाल, पासवर्ड मैनेजर को सर्वर क्रेडेंशियल की स्थिति के साथ सिंक रखने के लिए किया जाता है.

  • फ़ॉलबैक अनुभव: if (PublicKeyCredential.signalUnknownCredential) के ज़रिए गेट किया गया. अगर यह सुविधा काम नहीं करती है, तो बैकग्राउंड में पुष्टि करने की प्रोसेस को सिंक करने की सुविधा को बायपास कर दिया जाता है. इससे ब्राउज़र में कोई गड़बड़ी नहीं होती.

Easy JSON Serialization Fallback

हमेशा 'webauthn-polyfills' इंस्टॉल करें और उसे कॉन्टेक्स्ट में इंपोर्ट करें. ध्यान रखें कि जब तक PublicKeyCredential का इस्तेमाल किया जा सकता है, तब तक PublicKeyCredential.parseRequestOptionsFromJSON और PublicKeyCredential.prototype.toJSON का भी इस्तेमाल किया जा सकता है.

import 'webauthn-polyfills';