ثبت رمز عبور سمت سرور

بررسی اجمالی

در اینجا یک نمای کلی از مراحل کلیدی مربوط به ثبت کلید عبور آورده شده است:

جریان ثبت رمز عبور

  • گزینه هایی را برای ایجاد رمز عبور تعریف کنید. آنها را به مشتری ارسال کنید تا بتوانید آنها را به تماس ایجاد رمز عبور خود ارسال کنید: WebAuthn API با navigator.credentials.create در وب و credentialManager.createCredential در Android تماس می گیرد. پس از اینکه کاربر ایجاد رمز عبور را تأیید کرد، تماس ایجاد رمز عبور حل می‌شود و اعتبار PublicKeyCredential را برمی‌گرداند.
  • اعتبارنامه را تأیید کنید و آن را در سرور ذخیره کنید.

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

گزینه های ایجاد اعتبارنامه را ایجاد کنید

اولین قدمی که باید روی سرور بردارید، ایجاد یک شی PublicKeyCredentialCreationOptions است.

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

PublicKeyCredentialCreationOptions باید شامل همه چیزهایی باشد که برای ایجاد رمز عبور لازم است: اطلاعات مربوط به کاربر، در مورد RP، و یک پیکربندی برای ویژگی های اعتبارنامه ای که ایجاد می کنید. هنگامی که همه اینها را تعریف کردید، آنها را در صورت نیاز به تابعی در کتابخانه سمت سرور FIDO خود که مسئول ایجاد شی PublicKeyCredentialCreationOptions است، منتقل کنید.

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

  • rpId : برای پر کردن شناسه RP در سرور، از توابع یا متغیرهای سمت سرور استفاده کنید که نام میزبان برنامه وب شما را به شما می دهد، مانند example.com .
  • user.name و user.displayName : برای پر کردن این فیلدها، از اطلاعات جلسه کاربر وارد شده خود (یا اطلاعات حساب کاربری جدید، اگر کاربر در حال ایجاد یک کلید عبور هنگام ثبت نام است) استفاده کنید. user.name معمولا یک آدرس ایمیل است و برای RP منحصر به فرد است. user.displayName یک نام کاربر پسند است. توجه داشته باشید که همه پلتفرم ها displayName استفاده نمی کنند.
  • user.id : یک رشته تصادفی و منحصر به فرد که پس از ایجاد حساب ایجاد می شود. برخلاف نام کاربری که ممکن است قابل ویرایش باشد، باید دائمی باشد. شناسه کاربر یک حساب را شناسایی می کند، اما نباید حاوی اطلاعات شناسایی شخصی (PII) باشد . احتمالاً قبلاً یک شناسه کاربری در سیستم خود دارید، اما در صورت نیاز، یک شناسه به طور خاص برای کلیدهای عبور ایجاد کنید تا آن را عاری از هرگونه PII نگه دارید.
  • excludeCredentials : فهرستی از شناسه‌های اعتبارنامه‌های موجود برای جلوگیری از کپی کردن کلید عبور از ارائه‌دهنده کلید عبور. برای پر کردن این فیلد، اعتبار موجود برای این کاربر را در پایگاه داده خود جستجو کنید. جزئیات را در جلوگیری از ایجاد یک رمز عبور جدید در صورتی که از قبل وجود دارد، مرور کنید.
  • challenge : برای ثبت اعتبار، چالش مهم نیست مگر اینکه از گواهی استفاده کنید، روشی پیشرفته‌تر برای تأیید هویت ارائه‌دهنده کلید عبور و داده‌هایی که منتشر می‌کند. با این حال، حتی اگر از گواهی استفاده نکنید، چالش همچنان یک فیلد ضروری است. در این صورت، می توانید برای سادگی، این چالش را روی یک 0 قرار دهید. دستورالعمل‌های ایجاد چالش ایمن برای احراز هویت در احراز هویت رمز عبور سمت سرور موجود است.

رمزگذاری و رمزگشایی

PublicKeyCredentialCreationOptions ارسال شده توسط سرور
PublicKeyCredentialCreationOptions ارسال شده توسط سرور. challenge ، user.id و excludeCredentials.credentials باید در سمت سرور در base64URL کدگذاری شوند تا PublicKeyCredentialCreationOptions بتوان از طریق HTTPS تحویل داد.

PublicKeyCredentialCreationOptions شامل فیلدهایی است که ArrayBuffer هستند، بنابراین توسط JSON.stringify() پشتیبانی نمی شوند. این بدان معناست که در حال حاضر، برای ارائه PublicKeyCredentialCreationOptions از طریق HTTPS، برخی از فیلدها باید به صورت دستی روی سرور با استفاده از base64URL کدگذاری شوند و سپس روی کلاینت رمزگشایی شوند.

  • در سرور ، رمزگذاری و رمزگشایی معمولاً توسط کتابخانه سمت سرور FIDO شما انجام می شود.
  • در مشتری ، رمزگذاری و رمزگشایی باید در حال حاضر به صورت دستی انجام شود. در آینده آسان تر خواهد شد: روشی برای تبدیل گزینه ها به عنوان JSON به PublicKeyCredentialCreationOptions در دسترس خواهد بود. وضعیت پیاده سازی در کروم را بررسی کنید.

کد مثال: ایجاد گزینه های ایجاد اعتبار

ما از کتابخانه SimpleWebAuthn در مثال های خود استفاده می کنیم. در اینجا، ما ایجاد گزینه‌های اعتبار کلید عمومی را به تابع generateRegistrationOptions آن می‌سپاریم.

import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  verifyAuthenticationResponse
} from '@simplewebauthn/server';
import { isoBase64URL } from '@simplewebauthn/server/helpers';

router.post('/registerRequest', csrfCheck, sessionCheck, async (req, res) => {
  const { user } = res.locals;
  // 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 {
    // `excludeCredentials` prevents users from re-registering existing
    // credentials for a given passkey provider
    const excludeCredentials = [];
    const credentials = Credentials.findByUserId(user.id);
    if (credentials.length > 0) {
      for (const cred of credentials) {
        excludeCredentials.push({
          id: isoBase64URL.toBuffer(cred.id),
          type: 'public-key',
          transports: cred.transports,
        });
      }
    }

    // Generate registration options for WebAuthn create
    const options = generateRegistrationOptions({
      rpName: process.env.RP_NAME,
      rpID: process.env.HOSTNAME,
      userID: user.id,
      userName: user.username,
      userDisplayName: user.displayName || '',
      attestationType: 'none',
      excludeCredentials,
      authenticatorSelection: {
        authenticatorAttachment: 'platform',
        requireResidentKey: true
      },
    });

    // Keep the challenge in the session
    req.session.challenge = options.challenge;

    return res.json(options);
  } catch (e) {
    console.error(e);
    return res.status(400).send({ error: e.message });
  }
});

ذخیره کلید عمومی

PublicKeyCredentialCreationOptions ارسال شده توسط سرور
navigator.credentials.create یک شی PublicKeyCredential را برمی گرداند.

هنگامی که navigator.credentials.create با موفقیت در کلاینت حل شد، به این معنی است که یک رمز عبور با موفقیت ایجاد شده است. یک شی PublicKeyCredential برگردانده می شود.

شی PublicKeyCredential حاوی یک شی AuthenticatorAttestationResponse است که نشان دهنده پاسخ ارائه دهنده رمز عبور به دستور مشتری برای ایجاد یک رمز عبور است. این شامل اطلاعاتی در مورد اعتبار جدید است که به عنوان یک RP برای احراز هویت کاربر بعداً نیاز دارید. درباره AuthenticatorAttestationResponse در پیوست بیشتر بیاموزید: AuthenticatorAttestationResponse .

شی PublicKeyCredential را به سرور ارسال کنید. پس از دریافت آن، آن را تأیید کنید.

این مرحله تأیید را به کتابخانه سمت سرور FIDO خود تحویل دهید. معمولاً یک تابع ابزار برای این منظور ارائه می دهد. SimpleWebAuthn، برای مثال، verifyRegistrationResponse را ارائه می دهد. بیاموزید که در ضمیمه چه اتفاقی می افتد: تأیید پاسخ ثبت نام .

پس از تأیید موفقیت آمیز، اطلاعات اعتبارنامه را در پایگاه داده خود ذخیره کنید تا کاربر بعداً بتواند با کلید عبور مرتبط با آن اعتبار احراز هویت کند.

از یک جدول اختصاصی برای اعتبار کلید عمومی مرتبط با کلیدهای عبور استفاده کنید. یک کاربر فقط می‌تواند یک رمز عبور واحد داشته باشد، اما می‌تواند چندین کلید عبور داشته باشد - برای مثال، یک کلید عبور از طریق Apple iCloud Keychain و یکی از طریق Google Password Manager همگام‌سازی شده است.

در اینجا یک طرح نمونه وجود دارد که می توانید از آن برای ذخیره اطلاعات اعتبار استفاده کنید:

طرح واره پایگاه داده برای کلیدهای عبور

  • جدول کاربران :
    • user_id : شناسه کاربر اصلی. یک شناسه تصادفی، منحصر به فرد و دائمی برای کاربر. از این به عنوان کلید اصلی برای جدول کاربران خود استفاده کنید.
    • username . یک نام کاربری تعریف شده توسط کاربر، بالقوه قابل ویرایش.
    • passkey_user_id : شناسه کاربر بدون PII خاص با کلید عبور که توسط user.id در گزینه های ثبت نام شما نشان داده می شود. هنگامی که کاربر بعداً اقدام به احراز هویت می کند، احراز هویت این passkey_user_id را در پاسخ احراز هویت خود در userHandle در دسترس قرار می دهد. توصیه می کنیم passkey_user_id به عنوان کلید اصلی تنظیم نکنید. کلیدهای اولیه معمولاً در سیستم‌ها تبدیل به PII می‌شوند، زیرا به طور گسترده مورد استفاده قرار می‌گیرند.
  • جدول اعتبار کلید عمومی :
    • id : شناسه اعتبار. از این به عنوان کلید اصلی برای جدول اعتبارنامه کلید عمومی خود استفاده کنید.
    • public_key : کلید عمومی اعتبار.
    • passkey_user_id : از این به عنوان یک کلید خارجی برای ایجاد پیوند با جدول کاربران استفاده کنید.
    • backed_up : در صورتی که از یک کلید عبور توسط ارائه‌دهنده کلید عبور همگام‌سازی شده باشد، پشتیبان‌گیری می‌شود. اگر می‌خواهید در آینده برای کاربرانی که کلیدهای عبور backed_up دارند، گذرواژه‌ها را حذف کنید، ذخیره وضعیت پشتیبان مفید است. می‌توانید با بررسی پرچم‌ها در authenticatorData یا با استفاده از ویژگی کتابخانه سمت سرور FIDO که معمولاً برای دسترسی آسان به این اطلاعات در دسترس است، بررسی کنید که آیا از کلید عبور پشتیبانی می‌شود. ذخیره واجد شرایط بودن نسخه پشتیبان می تواند برای رسیدگی به سوالات احتمالی کاربر مفید باشد.
    • name : در صورت تمایل، یک نام نمایشی برای اعتبار برای اینکه کاربران بتوانند نام‌های سفارشی به اعتبارنامه‌ها بدهند.
    • transports : مجموعه ای از حمل و نقل . ذخیره سازی حمل و نقل برای تجربه کاربر احراز هویت مفید است. هنگامی که حمل و نقل در دسترس است، مرورگر می تواند مطابق با آن رفتار کند و رابط کاربری را نمایش دهد که با حمل و نقلی که ارائه دهنده رمز عبور برای برقراری ارتباط با مشتریان استفاده می کند، مطابقت دارد - به ویژه برای موارد استفاده از احراز هویت مجدد که در آن allowCredentials خالی نیست.

اطلاعات دیگر، از جمله مواردی مانند ارائه‌دهنده رمز عبور، زمان ایجاد اعتبار و آخرین زمان استفاده می‌تواند برای ذخیره‌سازی برای اهداف تجربه کاربر مفید باشد. در طراحی رابط کاربری Passkeys بیشتر بخوانید.

کد مثال: اطلاعات کاربری را ذخیره کنید

ما از کتابخانه SimpleWebAuthn در مثال های خود استفاده می کنیم. در اینجا، تأیید پاسخ ثبت نام را به تابع verifyRegistrationResponse آن می سپاریم.

import { isoBase64URL } from '@simplewebauthn/server/helpers';


router.post('/registerResponse', csrfCheck, sessionCheck, async (req, res) => {
  const expectedChallenge = req.session.challenge;
  const expectedOrigin = getOrigin(req.get('User-Agent'));
  const expectedRPID = process.env.HOSTNAME;
  const response = req.body;
  // This sample code is for registering a passkey for an existing,
  // signed-in user

  // 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 {
    // Verify the credential
    const { verified, registrationInfo } = await verifyRegistrationResponse({
      response,
      expectedChallenge,
      expectedOrigin,
      expectedRPID,
      requireUserVerification: false,
    });

    if (!verified) {
      throw new Error('Verification failed.');
    }

    const { credentialPublicKey, credentialID } = registrationInfo;

    // Existing, signed-in user
    const { user } = res.locals;
    
    // Save the credential
    await Credentials.update({
      id: base64CredentialID,
      publicKey: base64PublicKey,
      // Optional: set the platform as a default name for the credential
      // (example: "Pixel 7")
      name: req.useragent.platform, 
      transports: response.response.transports,
      passkey_user_id: user.passkey_user_id,
      backed_up: registrationInfo.credentialBackedUp
    });

    // Kill the challenge for this session
    delete req.session.challenge;

    return res.json(user);
  } catch (e) {
    delete req.session.challenge;

    console.error(e);
    return res.status(400).send({ error: e.message });
  }
});

پیوست: AuthenticatorAttestationResponse

AuthenticatorAttestationResponse شامل دو شی مهم است:

  • response.clientDataJSON یک نسخه JSON از داده های سرویس گیرنده است که در وب داده هایی است که توسط مرورگر مشاهده می شود. در صورتی که کلاینت یک برنامه اندروید باشد، شامل مبدا RP، چالش و androidPackageName است. به عنوان یک RP، خواندن clientDataJSON به شما امکان دسترسی به اطلاعاتی را می دهد که مرورگر در زمان درخواست create مشاهده کرده است.
  • response.attestationObject شامل دو اطلاعات است:
    • attestationStatement که مرتبط نیست مگر اینکه از گواهی استفاده کنید.
    • authenticatorData داده هایی است که توسط ارائه دهنده کلید عبور مشاهده می شود. به عنوان یک RP، خواندن authenticatorData به شما امکان دسترسی به داده‌هایی را می‌دهد که توسط ارائه‌دهنده کلید عبور مشاهده شده و در زمان درخواست create بازگردانده شده‌اند.

authenticatorData حاوی اطلاعات ضروری در مورد اعتبار کلید عمومی است که با رمز عبور تازه ایجاد شده مرتبط است:

  • خود اعتبار کلید عمومی و یک شناسه اعتبار منحصر به فرد برای آن.
  • شناسه RP مرتبط با اعتبار.
  • پرچم‌هایی که وضعیت کاربر را هنگام ایجاد رمز عبور توصیف می‌کنند: اینکه آیا کاربر واقعاً حضور داشته است و آیا کاربر با موفقیت تأیید شده است (به userVerification مراجعه کنید).
  • AAGUID ، که ارائه‌دهنده رمز عبور را شناسایی می‌کند. نمایش ارائه‌دهنده رمز عبور می‌تواند برای کاربران شما مفید باشد، به‌ویژه اگر یک کلید عبور برای سرویس شما در چندین ارائه‌دهنده رمز عبور ثبت شده باشد.

حتی اگر authenticatorData در attestationObject تو در تو قرار دارد، اطلاعاتی که در آن وجود دارد برای اجرای رمز عبور شما مورد نیاز است، چه از گواهی استفاده کنید یا نه. authenticatorData کدگذاری شده است و حاوی فیلدهایی است که در قالب باینری کدگذاری شده اند. کتابخانه سمت سرور شما معمولاً تجزیه و رمزگشایی را انجام می دهد. اگر از کتابخانه سمت سرور استفاده نمی‌کنید، از getAuthenticatorData() سمت کلاینت استفاده کنید تا مقداری تجزیه و رمزگشایی کار در سمت سرور را ذخیره کنید.

پیوست: تأیید پاسخ ثبت نام

در زیر هود، تأیید پاسخ ثبت نام شامل بررسی های زیر است:

  • اطمینان حاصل کنید که شناسه RP با سایت شما مطابقت دارد.
  • اطمینان حاصل کنید که مبدا درخواست یک منبع مورد انتظار برای سایت شما باشد (URL سایت اصلی، برنامه اندروید).
  • اگر به تأیید کاربر نیاز دارید، مطمئن شوید که پرچم تأیید کاربر authenticatorData.uv true است. بررسی کنید که پرچم حضور کاربر authenticatorData.up true باشد، زیرا حضور کاربر همیشه برای کلیدهای عبور لازم است.
  • بررسی کنید که مشتری می‌تواند چالشی را که شما برایش ارائه کرده‌اید ارائه کند. اگر از گواهی استفاده نمی کنید، این چک بی اهمیت است. با این حال، اجرای این بررسی بهترین روش است: در صورت تصمیم به استفاده از گواهی در آینده، کد شما آماده است.
  • اطمینان حاصل کنید که شناسه اعتبار هنوز برای هیچ کاربری ثبت نشده است.
  • بررسی کنید که الگوریتمی که ارائه‌دهنده کلید عبور برای ایجاد اعتبارنامه استفاده می‌کند، الگوریتمی است که شما فهرست کرده‌اید (در هر فیلد alg publicKeyCredentialCreationOptions.pubKeyCredParams ، که معمولاً در کتابخانه سمت سرور شما تعریف می‌شود و از دید شما قابل مشاهده نیست). این تضمین می کند که کاربران فقط می توانند با الگوریتم هایی ثبت نام کنند که شما انتخاب کرده اید تا اجازه دهید.

برای کسب اطلاعات بیشتر، کد منبع SimpleWebAuthn را برای verifyRegistrationResponse بررسی کنید یا به فهرست کامل تأییدیه‌ها در مشخصات بروید.

بعدی

احراز هویت رمز عبور سمت سرور