পাসকী রেজিস্ট্রেশন

এই নির্দেশিকায় বিস্তারিতভাবে বলা হয়েছে যে কীভাবে ব্যবহারকারীদের তাদের অ্যাকাউন্টের জন্য পাসকী রেজিস্টার করার সুবিধা দেওয়া যায়, যা অত্যন্ত সুরক্ষিত, ফিশিং-প্রতিরোধী পাসওয়ার্ডহীন সাইন-ইন বিকল্প প্রদান করে।

ডেটাবেস সংক্রান্ত প্রয়োজনীয়তা

পাসকী রেজিস্ট্রেশন সংক্রান্ত সুবিধা দিতে, আপনার ডেটাবেস ক্রেডেনশিয়াল টেবিলে নিম্নলিখিত ফিল্ডগুলি অবশ্যই সেভ করতে হবে:

export interface StoredPasskeyCredential {
  id: string; // Base64URL-encoded credential ID (unique lookup key)
  passkeyUserId: string; // Associated application user ID
  credentialPublicKey: string; // Base64URL-encoded public key used to verify assertion signatures
  credentialType: "public-key";
  credentialDeviceType: "singleDevice" | "multiDevice"; // Helps distinguish device-bound vs cloud-synced passkeys
  credentialBackedUp: boolean; // Boolean backup state reported by the authenticator
  aaguid: string; // Authenticator Attestation GUID
  providerIcon?: string; // Provider icon derived from the AAGUID registry (dark or light theme URLs)
  name: string; // Provider name derived from AAGUID registry
  transports: string[]; // Array of transport names (e.g. 'internal', 'hybrid') necessary for exclusion options
  lastUsedAt?: number; // Optional epoch timestamp of last sign-in
  registeredAt: number; // Registration epoch timestamp
  counter: number; // Authenticator sign-in signature counter used to prevent replay attacks
}

সার্ভার-সাইড

বিকল্প তৈরি করা

WebAuthn তৈরির প্যারামিটার জেনারেট করে এমন একটি এন্ডপয়েন্ট তৈরি করুন। হ্যান্ড-রোলিং ক্রিপ্টোগ্রাফির পরিবর্তে প্রতিটি বিভাগের স্ট্যান্ডার্ড অনুযায়ী যাচাই করা লাইব্রেরির উপর নির্ভর করুন।

  1. আগে থেকে নির্ধারিত RP ID ব্যবহার করা: কনস্ট্যান্ট স্ট্রিং হিসেবে আগে থেকে নির্ধারিত সঠিক RP ID ব্যবহার করুন।
  2. একটি সুরক্ষিত চ্যালেঞ্জ তৈরি করুন: সার্ভারে একটি হাই-এনট্রপি, ক্রিপ্টোগ্রাফিকভাবে সুরক্ষিত র‍্যান্ডম বাফার তৈরি করুন, ব্যবহারকারীর সেশনে এটি নিরাপদে স্টোর করুন এবং বিকল্প ডেলিভারির জন্য Base64URL হিসেবে এটি এনকোড করুন।
  3. ডুপ্লিকেট পাসকী এড়ানো: ব্যবহারকারীর আগে থেকে রেজিস্টার করা ক্রেডেনশিয়াল আইডি excludeCredentials বিকল্পের অ্যারেতে ম্যাপ করুন। এটি একই পাসকী প্রদানকারী অ্যাকাউন্টে প্রমাণীকরণকারীকে ডুপ্লিকেট ক্রেডেনশিয়াল রেজিস্টার করা থেকে আটকায়।
  4. খুঁজে নেওয়ার যোগ্য ক্রেডেনশিয়াল এনফোর্স করুন: খুঁজে নেওয়ার যোগ্য ক্রেডেনশিয়ালের অনুরোধ করতে authenticatorSelection বিকল্পে requireResidentKey: true ও residentKey: "required" সেট করুন, যা খুঁজে নেওয়ার যোগ্য সাইন-ইনের জন্য প্রয়োজন।
  5. ব্যবহারকারী যাচাইকরণ কনফিগার করুন: userVerification: "preferred" বা userVerification: "required" উল্লেখ করুন। অনেক সম্মতি সংক্রান্ত ব্যবহারের ক্ষেত্রে (যেমন, ফাইন্যান্স, স্বাস্থ্য পরিষেবা) তৈরি করার সময় ব্যবহারকারী যাচাইকরণ এনফোর্স করার জন্য 'required' প্রয়োজন।
  6. অ্যাটাচমেন্টের স্কোপ নির্ধারণ করা:
    • প্রোমোশন ফ্লো: স্ট্যান্ডার্ড পাসওয়ার্ড সাইন-ইন বা সাইন-আপ পরবর্তী প্রোমোশনের ঠিক পরেই পাসকী তৈরির প্রস্তাব দেওয়ার সময়, প্ল্যাটফর্ম অথেন্টিকেটর এনফোর্স করতে এবং এক্সটার্নাল নিরাপত্তা কী প্রম্পট বাইপাস করতে authenticatorAttachment: "platform" সেট করুন।
    • ম্যানেজমেন্ট ফ্লো: প্ল্যাটফর্ম যাচাইকারীর পাশাপাশি এক্সটার্নাল নিরাপত্তা কী কাজ করে এমন কোনও নির্দিষ্ট সেটিংস বা নিরাপত্তা প্যানেল থেকে কল করা হলে, authenticatorAttachment প্রপার্টি সম্পূর্ণভাবে বাদ দিন।
    • পরামর্শ: একটি এন্ডপয়েন্ট দিয়ে দুটি ফ্লো শর্তসাপেক্ষে ম্যানেজ করতে promotion: boolean অনুরোধ ফ্ল্যাগ গ্রহণ করুন।
// Options generation example
const options = {
  challenge: serverGeneratedBase64UrlChallenge, // Cryptographically random challenge
  rp: { id: "example.com", name: "Secure Application" },
  user: {
    id: userBase64UrlId, // Unique base64url string identifying the account
    name: "user@example.com",
    displayName: "Jane Doe",
  },
  pubKeyCredParams: [
    {
      type: "public-key",
      alg: -7,
    },
    {
      type: "public-key",
      alg: -257,
    },
  ],
  excludeCredentials: userExistingCredentials.map((cred) => ({
    type: "public-key",
    id: cred.id,
    transports: cred.transports,
  })),
  authenticatorSelection: {
    residentKey: "required",
    requireResidentKey: true,
    userVerification: "preferred",
    ...(isPromotionFlow && { authenticatorAttachment: "platform" }),
  },
};

যাচাইকরণ

  1. চ্যালেঞ্জ যাচাইকরণ: প্রত্যাশিত সেশন বাউন্ড চ্যালেঞ্জের বিপরীতে চ্যালেঞ্জটি নিরাপদে যাচাই করুন।
  2. ব্যবহারকারীর উপস্থিতি যাচাই করুন:
    • তৈরি করার সময় ব্যবহারকারীর ফিজিক্যাল উপস্থিতি কনফার্ম করতে, পার্স করা যাচাইকারীর ডেটায় ফেরত আসা ইউজার প্রেজেন্ট (UP) ফ্ল্যাগ true কিনা তা নিশ্চিত করুন।
  3. 'পছন্দের' জন্য যাচাইকরণ শিথিল করা:
    • তৈরি করার বিকল্প userVerification: "preferred" উল্লেখ করা থাকলে, সার্ভার-সাইড যাচাইকরণ কল অবশ্যই requireUserVerification: false-এর সাথে কনফিগার করতে হবে। অন্যথায়, যেসব যাচাইকারী ব্যবহারকারী যাচাইকরণ ছাড়াই রেজিস্টার করে (যেমন, স্ক্রিন লক বন্ধ করা আছে) সেগুলি সার্ভার যাচাইকরণে মিথ্যা ব্যর্থতা ট্রিগার করবে।

ক্লায়েন্ট-সাইড লজিক

  1. পৃষ্ঠা লোড হওয়ার সময় UI গেট করা:
    • পৃষ্ঠা লোড হওয়ার সময়, conditionalGet বা passkeyPlatformAuthenticator উপলভ্য না থাকলে, PublicKeyCredential.getClientCapabilities() কল করুন এবং "পাসকী তৈরি করুন" বোতাম বন্ধ করুন।
  2. তৈরি করার জন্য ইনভোক ও সিরিয়ালাইজ করুন: PublicKeyCredential.parseCreationOptionsFromJSON()-এর মাধ্যমে সার্ভার বিকল্প ডিকোড করুন এবং ফলাফল হিসেবে পাওয়া কনফিগারেশন navigator.credentials.create()-এ পাস করুন।
    • যাচাইকরণ এন্ডপয়েন্ট ফেচ করার আগে AuthenticatorAttestationResponse-কে সঠিক, JSON-সিরিয়ালাইজেবল অবজেক্টে এনকোড করতে credential.toJSON()-কে কল করুন।
  3. WebAuthn ব্যতিক্রম ম্যানেজ করা:
    • InvalidStateError: একটি ম্যাচিং পাসকী আগে থেকেই আছে (excludeCredentials-এর সাথে ম্যাচ করেছে)।
    • NotAllowedError: ব্যবহারকারী যাচাইকরণ পাসকী ডায়ালগ বাতিল করেছেন বা সেটির সময়সীমা শেষ হয়ে গেছে।
    • AbortError: অপারেশনটি বাতিল করা হয়েছে।
    • SecurityError: সুরক্ষিত অরিজিন (HTTPS) বা RP আইডি অমিল হওয়ার সমস্যা (কনফিগারেশন সংক্রান্ত সমস্যা)।
  4. সিগন্যাল API-এর জন্য Try/Catch Segregation:
    • try/catch ব্লকে সার্ভার যাচাইকরণ fetch() কল র‍্যাপ করুন। সার্ভার যাচাইকরণ ফেচ করা না গেলে (যেকোনও স্ট্যাটাস response.ok === false বা নেটওয়ার্ক থ্রো) signalUnknownCredential() কল করুন।
// optionsFetch and registerVerifyFetch are app-defined HTTP methods
import { optionsFetch, registerVerifyFetch } from "./api.js";

async function registerPasskey(isPromotion = false) {
  // Verify passkey capability and conditional UI are available
  const capabilities = await PublicKeyCredential.getClientCapabilities();
  if (
    !capabilities.passkeyPlatformAuthenticator ||
    !capabilities.conditionalGet
  ) {
    // Hide "Create passkey" buttons and fall back to password flows instead
    showStandardPasswordFallbackUI();
    return;
  }

  const creationOptionsJSON = await optionsFetch({ promotion: isPromotion });
  const publicKey =
    PublicKeyCredential.parseCreationOptionsFromJSON(creationOptionsJSON);

  let credential;
  try {
    // passkey prompt execution
    credential = await navigator.credentials.create({ publicKey });
  } catch (err) {
    if (err.name === "InvalidStateError") {
      console.log("A passkey already exists for this account.");
      alert("A passkey already exists for this account.");
    } else if (err.name === "SecurityError") {
      console.error("Configuration RP ID or Secure Context error.");
      alert("Configuration RP ID or Secure Context error.");
    } else if (err.name === "NotAllowedError") {
      console.log("User cancelled the passkey dialog.");
    } else if (err.name === "AbortError") {
      console.log("The creation operation has been aborted.");
    }
    return; // Safe API exit, do not signal unknown for standard WebAuthn cancels
  }

  // Server Verification phase (Segregated Try/Catch)
  let encodedResponse = credential.toJSON();
  try {
    const response = await registerVerifyFetch(encodedResponse);
    if (!response.ok) {
      // Server verification failed to verify/authenticate the credential (orphaned)
      await PublicKeyCredential.signalUnknownCredential({
        rpId, // RP ID must match the one defined on the server
        credentialId: encodedResponse.id, // Base64URL-encoded credential ID
      });
    }
  } catch (serverErr) {
    console.error("Server verification network failure:", serverErr);
    await publickeycredential.signalunknowncredential({
      rpId, // RP ID must match the one defined on the server
      credentialid: encodedresponse.id, // base64url-encoded credential id
    });
  }
}

ফলব্যাক স্ট্র্যাটেজি

সিগন্যাল API সিঙ্ক্রোনাইজেশন ফলব্যাক

WebAuthn Signal API (webauthn-signals) হল একটি প্রোগ্রেসিভ অপ্টিমাইজেশন যা পাসওয়ার্ড ম্যানেজারকে সার্ভার ক্রেডেনশিয়াল স্ট্যাটাসের সাথে সিঙ্ক করে রাখতে ব্যবহার করা হয়।

  • ফলব্যাক অভিজ্ঞতা: ব্রাউজার PublicKeyCredential.signalUnknownCredential-কে সাপোর্ট না করলে, ফিচার শনাক্তকরণ গেটিং (if (PublicKeyCredential.signalUnknownCredential))-এর মাধ্যমে কলটি অবশ্যই নিরাপদে বাইপাস করতে হবে এবং সার্ভার-সাইড যাচাইকরণ ম্যানেজার আপডেট ট্রিগার না করেই ব্যর্থতা লগ করে।

Easy JSON Serialization Fallback

সবসময় 'webauthn-polyfills' ইনস্টল করুন এবং কনটেক্সটে এটি ইমপোর্ট করুন। মনে রাখবেন, যতক্ষণ PublicKeyCredential কাজ করবে, ততক্ষণ PublicKeyCredential.parseCreationOptionsFromJSON এবং PublicKeyCredential.prototype.toJSON-ও কাজ করবে।

import 'webauthn-polyfills';