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

نمای کلی

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

جریان احراز هویت رمز عبور

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

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

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

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

// Example challenge, base64URL-encoded
weMLPOSx1VfSnMV6uPwDKbjGdKRMaUDGxeDEUTT5VN8

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

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

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

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

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

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

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

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

  • rpId : شما انتظار دارید اعتبار با کدام شناسه RP مرتبط باشد، برای مثال example.com . احراز هویت تنها در صورتی موفق می شود که شناسه RP که در اینجا ارائه می کنید با شناسه RP مرتبط با اعتبارنامه مطابقت داشته باشد. برای پر کردن شناسه RP، از همان مقدار شناسه RP که در publicKeyCredentialCreationOptions در حین ثبت اعتبار تنظیم کرده اید استفاده کنید.
  • challenge : بخشی از داده‌ای که ارائه‌دهنده رمز عبور امضا می‌کند تا ثابت کند کاربر رمز عبور را در زمان درخواست احراز هویت در اختیار دارد. جزئیات را در ایجاد چالش مرور کنید.
  • allowCredentials : آرایه ای از اعتبارنامه های قابل قبول برای این احراز هویت. یک آرایه خالی را ارسال کنید تا به کاربر اجازه دهید یک رمز عبور موجود را از لیست نشان داده شده توسط مرورگر انتخاب کند. برای جزئیات ، چالشی را از سرور RP و اعتبارنامه‌های کشف‌شده واکشی کنید.
  • userVerification : نشان می دهد که آیا تأیید کاربر با استفاده از قفل صفحه دستگاه "الزامی"، "ترجیح" یا "منصرف" است. بررسی واکشی چالش از سرور RP .
  • timeout : چه مدت (بر حسب میلی ثانیه) کاربر می تواند برای تکمیل احراز هویت طول بکشد. باید نسبتاً سخاوتمندانه و کوتاه‌تر از طول عمر challenge باشد. مقدار پیش‌فرض توصیه‌شده 5 دقیقه است، اما می‌توانید آن را افزایش دهید - تا 10 دقیقه، که هنوز در محدوده توصیه‌شده است. اگر انتظار داشته باشید که کاربران از گردش کار ترکیبی استفاده کنند که معمولاً کمی بیشتر طول می کشد، وقفه های زمانی طولانی منطقی است. اگر زمان عملیات تمام شود، یک NotAllowedError پرتاب می شود.

هنگامی که publicKeyCredentialRequestOptions را ایجاد کردید، آن را برای مشتری ارسال کنید.

publicKeyCredentialCreationOptions ارسال شده توسط سرور
گزینه های ارسال شده توسط سرور رمزگشایی challenge در سمت مشتری اتفاق می افتد.

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

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

import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  verifyAuthenticationResponse
} 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 را دریافت کنید.
    • در پایگاه داده خود پیدا کنید که کاربر چه کسی است. در مورد اعتبارنامه‌های قابل کشف، نمی‌دانید کاربری که درخواست احراز هویت می‌کند چه کسی است. برای فهمیدن، شما دو گزینه دارید:
      • گزینه 1: از response.userHandle در شی PublicKeyCredential استفاده کنید. در جدول Users ، به دنبال passkey_user_id باشید که با userHandle مطابقت دارد.
      • گزینه 2: id اعتبار موجود در شی PublicKeyCredential استفاده کنید. در جدول اعتبارات کلید عمومی ، به دنبال id اعتباری بگردید که id اعتبار موجود در شی PublicKeyCredential مطابقت دارد. سپس به دنبال کاربر مربوطه با استفاده از کلید خارجی passkey_user_id در جدول کاربران خود بگردید.
    • در پایگاه داده خود اطلاعات اعتبار کلید عمومی را پیدا کنید که با ادعای احراز هویتی که دریافت کرده اید مطابقت دارد. برای انجام این کار، در جدول اعتبارات کلید عمومی ، به دنبال id اعتباری بگردید که id اعتبار موجود در شی PublicKeyCredential مطابقت دارد.
  • ادعای احراز هویت را تأیید کنید. این مرحله تأیید را به کتابخانه سمت سرور FIDO خود بسپارید، که معمولاً یک تابع ابزار برای این منظور ارائه می دهد. SimpleWebAuthn، برای مثال، verifyAuthenticationResponse ارائه می دهد. در ضمیمه: تأیید پاسخ احراز هویت، در زیر پوشش چه اتفاقی می‌افتد، بیاموزید.

  • برای جلوگیری از حملات مجدد ، چالش را حذف کنید که آیا تأیید موفقیت آمیز است یا خیر .

  • کاربر را وارد کنید. اگر تأیید موفقیت آمیز بود، اطلاعات جلسه را به‌روزرسانی کنید تا کاربر را به‌عنوان واردشده علامت‌گذاری کنید. همچنین ممکن است بخواهید یک شی user به سرویس گیرنده برگردانید، بنابراین بخش جلویی می تواند از اطلاعات مرتبط با کاربر تازه وارد شده استفاده کند.

کد مثال: تایید و ورود کاربر

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

import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  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.');
    }
    // Base64URL decode some values
    const authenticator = {
      credentialPublicKey: isoBase64URL.toBuffer(cred.publicKey),
      credentialID: isoBase64URL.toBuffer(cred.id),
      transports: cred.transports,
    };

    // Verify the credential
    const { verified, authenticationInfo } = await verifyAuthenticationResponse({
      response,
      expectedChallenge,
      expectedOrigin,
      expectedRPID,
      authenticator,
      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 اجباری می‌کنید پیروی کرده باشد. اگر به تأیید کاربر نیاز دارید، مطمئن شوید که پرچم uv (تایید شده توسط کاربر) در authenticatorData true است. بررسی کنید که پرچم up (کاربر حاضر) در authenticatorData true باشد، زیرا حضور کاربر همیشه برای کلیدهای عبور لازم است.
  • امضا را تایید کنید برای تأیید امضا، شما نیاز دارید:
    • امضا، که چالش امضا شده است: response.signature
    • کلید عمومی، برای تأیید امضا با.
    • داده های امضا شده اصلی این داده هایی است که امضای آن باید تأیید شود.
    • الگوریتم رمزنگاری که برای ایجاد امضا استفاده شد.

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