اصالت‌سنجی گذرکلید سمت سرور

نمای کلی

در اینجا نمای کلی سطح بالایی از مراحل کلیدی مربوط به اصالت‌سنجی گذرکلید ارائه شده است:

جریان اصالت‌سنجی گذرکلید

  • چالش و گزینه‌های دیگر موردنیاز برای اصالت‌سنجی با گذرکلید را تعریف کنید. آن‌ها را به کارخواه ارسال کنید تا بتوانید آن‌ها را به تماس اصالت‌سنجی گذرکلیدتان (navigator.credentials.get در وب) منتقل کنید. پس‌از اینکه کاربر اصالت‌سنجی با گذرکلید را تأیید کرد، فراخوانی اصالت‌سنجی با گذرکلید حل‌وفصل می‌شود و اطلاعات اعتباری (PublicKeyCredential) را برمی‌گرداند. اطلاعات اعتباری حاوی ادعای اصالت‌سنجی است.
  • ادعای اصالت‌سنجی را درستی‌سنجی کنید.
  • اگر ادعای اصالت‌سنجی معتبر باشد، کاربر را اصالت‌سنجی کنید.

بخش‌های زیر جزئیات هر مرحله را شرح می‌دهد.

چالش را ایجاد کنید

در عمل، چالش آرایه‌ای از بایت‌های تصادفی است که به‌صورت شیء ArrayBuffer نشان داده می‌شود.

// Example challenge, base64URL-encoded
weMLPOSx1VfSnMV6uPwDKbjGdKRMaUDGxeDEUTT5VN8

برای اطمینان از اینکه چالش به هدف خود می‌رسد، باید:

  1. مطمئن شوید که هر چالش هرگز بیش‌از یک بار استفاده نشود. در هر تلاش برای ورود به سیستم، چالش جدیدی ایجاد کنید. پس‌از هر تلاش برای ورود به سیستم، چه موفقیت‌آمیز باشد و چه ناموفق، چالش را دور بیندازید. همچنین چالش را پس‌از مدت‌زمان مشخصی کنار بگذارید. هرگز چالش یکسانی را بیش‌از یک بار در پاسخ نپذیرید.
  2. مطمئن شوید چالش ازنظر رمزنگاری ایمن باشد. حدس زدن چالش باید عملاً غیرممکن باشد. برای ایجاد یک چالش امن ازنظر رمزنگاری در سمت سرور، بهتر است به یک کتابخانه سمت سرور FIDO که به آن اعتماد دارید تکیه کنید. اگر به‌جای آن چالش‌های خودتان را ایجاد می‌کنید، از عملکرد رمزنگاری داخلی موجود در پشته فناوری‌تان استفاده کنید یا به‌دنبال کتابخانه‌هایی باشید که برای موارد استفاده رمزنگاری طراحی شده‌اند. نمونه‌ها شامل iso-crypto در Node.js، یا secrets در Python است. طبق مشخصات، چالش باید حداقل ۱۶ بایت طول داشته باشد تا ایمن درنظر گرفته شود.

پس‌از ایجاد چالش، آن را در جلسه کاربر ذخیره کنید تا بعداً آن را درستی‌سنجی کنید.

ایجاد گزینه‌های درخواست اطلاعات کاربری

گزینه‌های درخواست اطلاعات کاربری را به‌عنوان شیء publicKeyCredentialRequestOptions ایجاد کنید.

برای انجام این کار، به کتابخانه سمت سرور FIDO خود تکیه کنید. معمولاً تابع سودمندی‌ای ارائه می‌دهد که می‌تواند این گزینه‌ها را برای شما ایجاد کند. برای مثال، SimpleWebAuthn generateAuthenticationOptions را ارائه می‌دهد.

publicKeyCredentialRequestOptions باید حاوی همه اطلاعات لازم برای اصالت‌سنجی با گذرکلید باشد. این اطلاعات را به تابع موجود در کتابخانه سمت سرور FIDO که مسئول ایجاد شیء publicKeyCredentialRequestOptions است ارسال کنید.

برخی‌از فیلدهای publicKeyCredentialRequestOptions می‌توانند ثابت باشند. سایر موارد باید به‌صورت پویا در سرور تعریف شوند:

  • rpId: انتظار دارید اعتبارنامه با کدام شناسه طرف محدود مرتبط شود، برای مثال 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 موردانتظار را دریافت کنید.
    • در پایگاه داده‌تان پیدا کنید که کاربر کیست. درخصوص اطلاعات اعتباری قابل‌کشف، نمی‌دانید کاربری که درخواست اصالت‌سنجی می‌کند کیست. برای اطلاع از این موضوع، دو گزینه دارید:
      • گزینه ۱: از response.userHandle در شیء PublicKeyCredential استفاده کنید. در جدول کاربران، به‌دنبال passkey_user_id که با userHandle مطابقت دارد بگردید.
      • گزینه ۲: از اطلاعات اعتباری id موجود در شیء PublicKeyCredential استفاده کنید. در جدول اعتبارنامه کلید عمومی، به‌دنبال اعتبارنامه id بگردید که با اعتبارنامه id موجود در شیء PublicKeyCredential مطابقت داشته باشد. سپس بااستفاده از کلید خارجی passkey_user_id، کاربر مربوطه را در جدول کاربران پیدا کنید.
    • در پایگاه داده‌تان، اطلاعات اطلاعات کاربری کلید عمومی را که با ادعای اصالت‌سنجی دریافتی‌تان مطابقت دارد پیدا کنید. برای انجام این کار، در جدول اعتبارنامه‌های کلید عمومی، به‌دنبال اعتبارنامه id بگردید که با اعتبارنامه id موجود در شیء PublicKeyCredential مطابقت داشته باشد.
  • ادعای اصالت‌سنجی را تأیید کنید. این مرحله درستی‌سنجی را به کتابخانه سمت سرور 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 });
  }
});

پیوست: درستی‌سنجی پاسخ اصالت‌سنجی

درستی‌سنجی پاسخ اصالت‌سنجی شامل بررسی‌های زیر است:

برای کسب اطلاعات بیشتر درباره این مراحل، کد منبع SimpleWebAuthn برای verifyAuthenticationResponse را بررسی کنید یا فهرست کامل درستی‌سنجی‌ها را در مشخصات بررسی کنید.