نمای کلی
در اینجا یک نمای کلی از مراحل کلیدی مربوط به ثبت کلید عبور آورده شده است:
- گزینه هایی را برای ایجاد رمز عبور تعریف کنید. آنها را به مشتری ارسال کنید تا بتوانید آنها را به تماس ایجاد رمز عبور خود ارسال کنید: 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
شامل فیلدهایی است که 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 });
}
});
ذخیره کلید عمومی
هنگامی که 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
بررسی کنید یا به فهرست کامل تأییدیهها در مشخصات بروید.