نمای کلی
در اینجا یک نمای کلی از مراحل کلیدی مربوط به احراز هویت رمز عبور آورده شده است:
- چالش و سایر گزینه های مورد نیاز برای احراز هویت با کلید عبور را تعریف کنید. آنها را برای مشتری ارسال کنید، تا بتوانید آنها را به تماس احراز هویت رمز عبور خود (
navigator.credentials.get
در وب) ارسال کنید. پس از اینکه کاربر احراز هویت رمز عبور را تأیید کرد، تماس احراز هویت با کلید عبور حل می شود و یک اعتبار (PublicKeyCredential
) برمی گرداند. اعتبار شامل یک ادعای احراز هویت است.
- ادعای احراز هویت را تأیید کنید.
- اگر ادعای احراز هویت معتبر است، کاربر را احراز هویت کنید.
بخش های زیر به جزئیات هر مرحله می پردازد.
چالش را ایجاد کنید
در عمل، چالش آرایه ای از بایت های تصادفی است که به عنوان یک شی ArrayBuffer
نشان داده می شود.
// Example challenge, base64URL-encoded
weMLPOSx1VfSnMV6uPwDKbjGdKRMaUDGxeDEUTT5VN8
برای اطمینان از اینکه چالش هدف خود را برآورده می کند، باید:
- اطمینان حاصل کنید که یک چالش هرگز بیش از یک بار استفاده نمی شود. در هر تلاش برای ورود به سیستم، یک چالش جدید ایجاد کنید. پس از هر تلاش برای ورود به سیستم، خواه موفقیت آمیز باشد یا ناموفق، چالش را کنار بگذارید. پس از مدت زمان مشخصی نیز چالش را کنار بگذارید. هرگز یک چالش را در یک پاسخ بیش از یک بار نپذیرید.
- اطمینان حاصل کنید که چالش از نظر رمزنگاری ایمن است . حدس زدن یک چالش باید عملا غیرممکن باشد . برای ایجاد چالش ایمن رمزنگاری در سمت سرور، بهتر است به یک کتابخانه سمت سرور 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
را ایجاد کردید، آن را برای مشتری ارسال کنید.
کد مثال: گزینه های درخواست اعتبار را ایجاد کنید
ما از کتابخانه 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
را برمی گرداند.
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
در جدول کاربران خود بگردید.
- گزینه 1: از
- در پایگاه داده خود اطلاعات اعتبار کلید عمومی را پیدا کنید که با ادعای احراز هویتی که دریافت کرده اید مطابقت دارد. برای انجام این کار، در جدول اعتبارات کلید عمومی ، به دنبال
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
بررسی کنید یا به فهرست کامل تأییدیهها در مشخصات بروید.