সার্ভার-সাইড পাসকী যাচাইকরণ

এক নজরে

পাসকী যাচাইকরণের সাথে যুক্ত মূল ধাপগুলির একটি হাই-লেভেল ওভারভিউ এখানে দেওয়া হল:

পাসকী যাচাইকরণ ফ্লো

  • পাসকী দিয়ে যাচাই করার জন্য প্রয়োজনীয় চ্যালেঞ্জ ও অন্যান্য বিকল্প নির্ধারণ করুন। সেগুলি ক্লায়েন্টে পাঠান, যাতে আপনি সেগুলি আপনার পাসকী যাচাইকরণ কলে (ওয়েবে navigator.credentials.get) পাস করতে পারেন। ব্যবহারকারী পাসকী যাচাইকরণ কনফার্ম করার পরে, পাসকী যাচাইকরণ কল সমাধান করা হয় এবং একটি ক্রেডেনশিয়াল (PublicKeyCredential) ফিরিয়ে দেয়। ক্রেডেনশিয়ালে একটি যাচাইকরণ অ্যাসারশন থাকে।
  • যাচাইকরণের অ্যাসারশন যাচাই করুন।
  • যাচাইকরণ অ্যাসারশন সঠিক হলে, ব্যবহারকারীকে যাচাই করুন।

নিম্নলিখিত বিভাগগুলি প্রতিটি ধাপের নির্দিষ্ট বিবরণ দেয়।

চ্যালেঞ্জ তৈরি করুন

বাস্তবে, চ্যালেঞ্জ হল র‍্যান্ডম বাইটের একটি অ্যারে, যা ArrayBuffer অবজেক্ট হিসেবে দেখানো হয়।

// Example challenge, base64URL-encoded
weMLPOSx1VfSnMV6uPwDKbjGdKRMaUDGxeDEUTT5VN8

চ্যালেঞ্জটি যাতে তার উদ্দেশ্য পূরণ করে তা নিশ্চিত করতে, আপনাকে অবশ্যই:

  1. একই চ্যালেঞ্জ যেন একাধিকবার ব্যবহার না করা হয় তা নিশ্চিত করুন। প্রতিবার সাইন-ইন করার চেষ্টা করলে নতুন চ্যালেঞ্জ জেনারেট করুন। সাইন-ইন করার প্রতিটি প্রচেষ্টার পরে চ্যালেঞ্জ বাতিল করুন, তা সফল হোক বা ব্যর্থ। এছাড়াও, নির্দিষ্ট সময়সীমা পরে চ্যালেঞ্জ বাতিল করুন। কোনও উত্তরে একই চ্যালেঞ্জ একাধিকবার গ্রহণ করবেন না।
  2. চ্যালেঞ্জটি ক্রিপ্টোগ্রাফিকভাবে সুরক্ষিত কিনা তা নিশ্চিত করুন। চ্যালেঞ্জ এমন হতে হবে যা অনুমান করা কার্যত অসম্ভব। সার্ভার-সাইডে ক্রিপ্টোগ্রাফিকভাবে সুরক্ষিত চ্যালেঞ্জ তৈরি করতে, আপনার বিশ্বস্ত FIDO সার্ভার-সাইড লাইব্রেরির উপর নির্ভর করা সবচেয়ে ভালো। এর পরিবর্তে, আপনি নিজের চ্যালেঞ্জ তৈরি করলে, আপনার টেক স্ট্যাকে উপলভ্য বিল্ট-ইন ক্রিপ্টোগ্রাফিক কার্যকারিতা ব্যবহার করুন অথবা ক্রিপ্টোগ্রাফিক ব্যবহারের জন্য ডিজাইন করা লাইব্রেরি খুঁজুন। উদাহরণ হিসেবে Node.js-এ iso-crypto বা Python-এ secrets-এর কথা বলা যেতে পারে। স্পেসিফিকেশন অনুযায়ী, চ্যালেঞ্জটি নিরাপদ হিসেবে বিবেচিত হওয়ার জন্য কমপক্ষে ১৬ বাইট দীর্ঘ হতে হবে।

আপনি চ্যালেঞ্জ তৈরি করার পরে, পরে যাচাই করার জন্য এটি ব্যবহারকারীর সেশনে সেভ করুন।

ক্রেডেনশিয়াল অনুরোধের বিকল্প তৈরি করুন

publicKeyCredentialRequestOptions অবজেক্ট হিসেবে ক্রেডেনশিয়াল অনুরোধের বিকল্প তৈরি করুন।

এটি করতে, আপনার FIDO সার্ভার-সাইড লাইব্রেরির উপর নির্ভর করুন। এটি সাধারণত একটি ইউটিলিটি ফাংশন অফার করবে যা আপনার জন্য এই বিকল্পগুলি তৈরি করতে পারে। SimpleWebAuthn, উদাহরণস্বরূপ, generateAuthenticationOptions অফার করে।

publicKeyCredentialRequestOptions-এ পাসকী যাচাইকরণের জন্য প্রয়োজনীয় সব তথ্য থাকতে হবে। আপনার FIDO সার্ভার-সাইড লাইব্রেরির ফাংশনে এই তথ্য পাস করুন যা publicKeyCredentialRequestOptions অবজেক্ট তৈরি করার জন্য দায়ী।

publicKeyCredentialRequestOptions-এর কিছু ফিল্ড কনস্ট্যান্ট হতে পারে। অন্যান্যগুলি সার্ভারে ডায়নামিকভাবে সংজ্ঞায়িত করা উচিত:

  • rpId: আপনি কোন RP আইডির সাথে ক্রেডেনশিয়াল অ্যাসোসিয়েট করতে চান, যেমন example.com। আপনি এখানে যে RP আইডি দিয়েছেন সেটি ক্রেডেনশিয়ালের সাথে যুক্ত RP আইডির সাথে মিললে তবেই যাচাইকরণ সম্পূর্ণ হবে। RP আইডি পপুলেট করতে, ক্রেডেনশিয়াল রেজিস্ট্রেশনের সময় publicKeyCredentialCreationOptions-এ সেট করা RP আইডির মতো একই ভ্যালু ব্যবহার করুন।
  • challenge: ডেটার একটি অংশ যা পাসকী প্রদানকারী সাইন করবে যাতে প্রমাণ করা যায় যে যাচাইকরণের অনুরোধের সময় ব্যবহারকারীর কাছে পাসকী আছে। চ্যালেঞ্জ তৈরি করুন বিকল্পে গিয়ে বিবরণ পর্যালোচনা করুন।
  • allowCredentials: এই যাচাইকরণের জন্য গ্রহণযোগ্য ক্রেডেনশিয়ালের অ্যারে। ব্রাউজারের দেখানো তালিকা থেকে ব্যবহারকারীকে উপলভ্য পাসকী বেছে নিতে দেওয়ার জন্য একটি খালি অ্যারে পাস করুন। বিস্তারিত জানতে RP সার্ভার থেকে চ্যালেঞ্জ ফেচ করুন এবং খুঁজে পাওয়ার যোগ্য ক্রেডেনশিয়াল সম্পর্কে বিস্তারিত তথ্য পর্যালোচনা করুন।
  • userVerification: ডিভাইসের স্ক্রিন লক ব্যবহার করে ব্যবহারকারী যাচাইকরণ "প্রয়োজন", "পছন্দসই" নাকি "নিরুৎসাহিত" তা নির্দেশ করে। RP সার্ভার থেকে চ্যালেঞ্জ ফেচ করুন বিকল্পটি পর্যালোচনা করুন।
  • timeout: ব্যবহারকারী যাচাইকরণ সম্পূর্ণ করতে কত সময় (মিলিসেকেন্ডে) নিতে পারেন। এটি যুক্তিসঙ্গতভাবে উদার এবং challenge-এর লাইফটাইমের চেয়ে ছোট হতে হবে। সাজেস্ট করা ডিফল্ট ভ্যালু হল ৫ মিনিট, তবে আপনি এটি বাড়াতে পারেন — ১০ মিনিট পর্যন্ত, যা এখনও সাজেস্ট করা রেঞ্জের মধ্যে রয়েছে। আপনি যদি আশা করেন যে ব্যবহারকারীরা হাইব্রিড ওয়ার্কফ্লো ব্যবহার করবেন, তাহলে দীর্ঘ টাইমআউট অর্থপূর্ণ হয়, যা সাধারণত একটু বেশি সময় নেয়। অপারেশন টাইম-আউট হয়ে গেলে, একটি 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। RP-তে পাসকী দিয়ে যাচাই করার জন্য যা প্রয়োজন তা তৈরি করার জন্য ক্লায়েন্টের নির্দেশ অনুযায়ী পাসকী প্রদানকারীর উত্তর এটি দেখায়। এতে রয়েছে:

  • response.authenticatorDataএবংresponse.clientDataJSON, যেমন পাসকী রেজিস্ট্রেশন ধাপে।
  • response.signature যাতে এইসব ভ্যালুর উপর একটি স্বাক্ষর থাকে।

সার্ভারে PublicKeyCredential অবজেক্ট পাঠান।

সার্ভারে, নিম্নলিখিতগুলি করুন:

ডেটাবেস স্কিমা
সাজেস্ট করা ডেটাবেস স্কিমা। সার্ভার-সাইড পাসকী রেজিস্ট্রেশন থেকে এই ডিজাইন সম্পর্কে আরও জানুন।
  • অ্যাসার্শন যাচাই ও ব্যবহারকারীকে যাচাইকরণ করার জন্য প্রয়োজনীয় তথ্য সংগ্রহ করুন:
    • আপনি যাচাইকরণের বিকল্প জেনারেট করার সময় সেশনে সেভ করা প্রত্যাশিত চ্যালেঞ্জ পান।
    • প্রত্যাশিত উৎপত্তি ও RP আইডি পান।
    • আপনার ডেটাবেসে ব্যবহারকারী কে তা খুঁজে বের করুন। খুঁজে নেওয়ার যোগ্য ক্রেডেনশিয়ালের ক্ষেত্রে, আপনি জানেন না যে কোন ব্যবহারকারী যাচাইকরণের অনুরোধ করছেন। এটি খুঁজে পেতে, আপনার কাছে দুটি বিকল্প আছে:
      • বিকল্প ১: PublicKeyCredential অবজেক্টে response.userHandle ব্যবহার করো। ব্যবহারকারী টেবিলে, passkey_user_id খুঁজুন যা userHandle-এর সাথে ম্যাচ করে।
      • বিকল্প ২: 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 });
  }
});

পরিশিষ্ট: যাচাইকরণ উত্তরের যাচাইকরণ

যাচাইকরণ সংক্রান্ত উত্তর যাচাই করার মধ্যে নিম্নলিখিত চেকগুলি অন্তর্ভুক্ত থাকে:

  • RP আইডি আপনার সাইটের সাথে মিলছে কিনা তা নিশ্চিত করুন।
  • অনুরোধের উৎস আপনার সাইটের সাইন-ইন উৎসের সাথে মিলছে কিনা তা নিশ্চিত করুন। Android অ্যাপের জন্য, উৎপত্তি যাচাই করুন বিকল্পটি পর্যালোচনা করুন।
  • ডিভাইসটি আপনার দেওয়া চ্যালেঞ্জের উত্তর দিতে পেরেছে কিনা তা চেক করুন।
  • যাচাই করুন যে যাচাইকরণের সময়, ব্যবহারকারী RP হিসেবে আপনার বাধ্যতামূলক করা প্রয়োজনীয়তাগুলি অনুসরণ করেছেন। ব্যবহারকারী যাচাইকরণ প্রয়োজন হলে, authenticatorData-এ uv (ব্যবহারকারী যাচাই করা হয়েছে) ফ্ল্যাগ true কিনা তা নিশ্চিত করুন। authenticatorData-এ up (ব্যবহারকারীর উপস্থিতি) ফ্ল্যাগ true কিনা তা চেক করুন, কারণ পাসকী ব্যবহারের জন্য ব্যবহারকারীর উপস্থিতি সবসময় প্রয়োজন।
  • স্বাক্ষর যাচাই করুন। স্বাক্ষর যাচাই করতে, আপনার এগুলি প্রয়োজন:
    • স্বাক্ষর, যা হল স্বাক্ষরিত চ্যালেঞ্জ: response.signature
    • পাবলিক কী, যার মাধ্যমে স্বাক্ষর যাচাই করা হয়।
    • আসল স্বাক্ষরিত ডেটা। এই ডেটার স্বাক্ষর যাচাই করা হবে।
    • স্বাক্ষর তৈরি করতে যে ক্রিপ্টোগ্রাফিক অ্যালগরিদম ব্যবহার করা হয়েছে।

এইসব ধাপ সম্পর্কে আরও জানতে, SimpleWebAuthn-এর verifyAuthenticationResponse-এর জন্য সোর্স কোড চেক করুন অথবা স্পেসিফিকেশন-এ যাচাইকরণের সম্পূর্ণ তালিকা দেখুন।