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

- گزینههای ایجاد گذرکلید را تعریف کنید. آنها را به مشتری ارسال کنید تا بتوانید آنها را به فراخوانی ایجاد گذرکلیدتان منتقل کنید: فراخوانی 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معمولاً نشانی ایمیل است و برای «طرف اعتمادکننده» منحصربهفرد است.user.displayNameنام کاربرپسند است. توجه داشته باشید که همه پلاتفرمها ازdisplayNameاستفاده نمیکنند.-
user.id: رشتهای تصادفی و یکتا که هنگام ایجاد حساب تولید میشود. این شناسه باید دائمی باشد، برخلاف نام کاربری که ممکن است قابلویرایش باشد. شناسه کاربر حسابی را شناسایی میکند، اما نباید حاوی هیچگونه اطلاعات شناساننده شخصی (PII) باشد. احتمالاً ازقبل شناسه کاربری در سیستم خود دارید، اما درصورت نیاز، شناسه کاربری را بهطور خاص برای گذرکلیدها ایجاد کنید تا عاری از هرگونه اطلاعات شناساننده شخصی باشد. excludeCredentials: فهرستی از شناسههای اطلاعات کاربری موجود برای جلوگیری از تکرار گذرکلید از ارائهدهنده گذرکلید. برای پر کردن این فیلد، اطلاعات کاربری موجود برای این کاربر را در پایگاه دادهتان جستجو کنید. جزئیات را در جلوگیری از ایجاد گذرکلید جدید درصورت وجود گذرکلید مرور کنید.challenge: برای ثبت اطلاعات کاربری، چالش مربوط نیست، مگراینکه از گواهی استفاده کنید که تکنیک پیشرفتهتری برای درستیسنجی هویت ارائهدهنده گذرکلید و دادههای منتشرشده از آن است. بااینحال، حتی اگر از گواهی استفاده نمیکنید، چالش همچنان فیلد الزامی است. دستورالعملهای ایجاد چالش امن برای اصالتسنجی در اصالتسنجی گذرکلید سمت سرور دردسترس است.
کدبندی و کدگشایی
PublicKeyCredentialCreationOptions ارسالشده توسط سرور. فیلدهای دودویی مانند challenge و user.id در سمت سرور به base64URL کدگذاری میشوند تا گزینهها بتوانند بهعنوان JSON ازطریق HTTPS ارائه شوند.شیء استاندارد WebAuthn PublicKeyCredentialCreationOptions شامل فیلدهایی است که ArrayBuffer هستند و نمیتوانند مستقیماً با JSON.stringify() سریالسازی شوند. برای ارسال این گزینهها ازطریق HTTPS، فیلدهای دودویی باید بهصورت رشتههای base64URL کدگذاری شوند:
- در سرور، کتابخانه سمت سرور FIDO شما (مانند SimpleWebAuthn) بهطور خودکار یک شیء گزینههای قابلسریالسازی JSON (مانند
PublicKeyCredentialCreationOptionsJSON) با فیلدهای کدگذاریشده Base64URL تولید میکند. - در سمت کارخواه، مرورگرهای نو از
PublicKeyCredential.parseCreationOptionsFromJSON()برای تبدیل مستقیم گزینههای JSON بهPublicKeyCredentialCreationOptionsپشتیبانی میکنند. هنگام استفاده از کتابخانههای سمت کارخواه مانند@simplewebauthn/browser، توابعی مانندstartRegistration({ optionsJSON: options })این تبدیل را بهطور خودکار انجام میدهند.
کد نمونه: ایجاد گزینههای ایجاد اعتبارنامه
در مثالهایمان از کتابخانه SimpleWebAuthn استفاده میکنیم. در اینجا، ایجاد گزینههای اطلاعات کاربری کلید عمومی را به تابع generateRegistrationOptions آن واگذار میکنیم.
import {
generateRegistrationOptions,
verifyRegistrationResponse,
} from '@simplewebauthn/server';
import { isoBase64URL, isoUint8Array } 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: cred.id,
type: 'public-key',
transports: cred.transports,
});
}
}
// Generate registration options for WebAuthn create
const options = await generateRegistrationOptions({
rpName: process.env.RP_NAME,
rpID: process.env.HOSTNAME,
userID: isoUint8Array.fromUTF8String(user.id),
userName: user.username,
userDisplayName: user.displayName || '',
attestationType: 'none',
excludeCredentials,
authenticatorSelection: {
authenticatorAttachment: 'platform',
residentKey: 'required',
userVerification: 'preferred',
},
});
// 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 را برمیگرداند.وقتی navigator.credentials.create باموفقیت در کارخواه حل شود، یعنی گذرکلید باموفقیت ایجاد شده است. یک شیء PublicKeyCredential برگردانده میشود.
شیء PublicKeyCredential حاوی شیء AuthenticatorAttestationResponse است که نشاندهنده پاسخ ارائهدهنده گذرکلید به دستور مشتری برای ایجاد گذرکلید است. این پاسخ حاوی اطلاعاتی درباره اطلاعات اعتباری جدید است که شما بهعنوان «طرف معتمد» برای اصالتسنجی کاربر در آینده به آن نیاز دارید. درباره AuthenticatorAttestationResponse در پیوست: AuthenticatorAttestationResponse بیشتر بدانید.
شیء PublicKeyCredential را به سرور ارسال کنید. وقتی آن را دریافت کردید، درستیسنجیاش کنید.
این مرحله درستیسنجی را به کتابخانه سمت سرور FIDO خود واگذار کنید. معمولاً برای این منظور یک تابع سودمندی ارائه میدهد. برای مثال، SimpleWebAuthn verifyRegistrationResponse را ارائه میدهد. در پیوست: درستیسنجی پاسخ ثبتنام با آنچه در پشت صحنه اتفاق میافتد آشنا شوید.
پساز موفقیتآمیز بودن درستیسنجی، اطلاعات اعتبارنامه را در پایگاه دادهتان ذخیره کنید تا کاربر بتواند بعداً با گذرکلید منسوب به آن اعتبارنامه اصالتسنجی کند.
از جدول اختصاصی برای اطلاعات کاربری کلید عمومی مرتبط با گذرکلیدها استفاده کنید. کاربر فقط میتواند یک گذرواژه داشته باشد، اما میتواند چند گذرکلید داشته باشد — برای مثال، یک گذرکلید همگامسازیشده ازطریق Apple iCloud Keychain و یک گذرکلید ازطریق «مدیر گذرواژه Google».
در اینجا نمونهای از طرحوارهای که میتوانید برای ذخیره اطلاعات اعتبارنامه استفاده کنید آورده شده است:

- جدول کاربران:
user_id: شناسه کاربر اصلی. شناسه دائمی، یکتا، و تصادفی برای کاربر. از این بهعنوان کلید اصلی برای جدول کاربران استفاده کنید.username. نام کاربری تعریفشده توسط کاربر، احتمالاً قابلویرایش.passkey_user_id: شناسه کاربر بدون اطلاعات شناساننده شخصی مختص گذرکلید، که باuser.idدر گزینههای ثبتنام شما نشان داده میشود. وقتی کاربر بعداً تلاش میکند اصالتسنجی کند، اصالتسنج اینpasskey_user_idرا در پاسخ اصالتسنجی خود درuserHandleدردسترس قرار میدهد. توصیه میکنیمpasskey_user_idرا بهعنوان کلید اصلی تنظیم نکنید. کلیدهای اصلی معمولاً در سیستمها به اطلاعات شناساننده شخصی واقعی تبدیل میشوند، زیرا بهطور گسترده استفاده میشوند.
- جدول اعتبارنامه کلید عمومی:
id: شناسه اطلاعات اعتباری. از این بهعنوان کلید اصلی برای جدول اعتبارنامه کلید عمومی استفاده کنید.-
public_key: کلید عمومی اطلاعات کاربری. -
passkey_user_id: از این بهعنوان کلید خارجی برای ایجاد پیوند با جدول کاربران استفاده کنید. backed_up: اگر گذرکلید توسط ارائهدهنده گذرکلید همگامسازی شود، پشتیبانگیری میشود. ذخیره کردن وضعیت پشتیبانگیری درصورتی مفید است که بخواهید در آینده گذرواژهها را برای کاربرانی که گذرکلیدbacked_upدارند حذف کنید. میتوانید با بررسی پرچم BE درauthenticatorDataیا بااستفاده از ویژگی کتابخانه سمت سرور FIDO که معمولاً برای دسترسی آسان به این اطلاعات دردسترس است، بررسی کنید که آیا از گذرکلید پشتیبانگیری شده است یا نه. ذخیره کردن واجدشرایط بودن پشتیبانگیری میتواند برای رسیدگی به پرسشهای احتمالی کاربر مفید باشد.name: بهصورت اختیاری، نام نمایشی برای اطلاعات کاربری تا کاربران بتوانند نامهای سفارشی به اطلاعات کاربری بدهند.transports: آرایهای از انتقالها. ذخیره کردن انتقالها برای تجربه کاربری اصالتسنجی مفید است. وقتی انتقالها دردسترس باشند، مرورگر میتواند مطابق با آن رفتار کند و میانای کاربریای را نمایش دهد که با انتقالی که ارائهدهنده گذرکلید برای برقراری ارتباط با کارخواهان استفاده میکند مطابقت داشته باشد—بهویژه برای موارد استفاده از اصالتسنجی مجدد کهallowCredentialsخالی نیست.
اطلاعات دیگری نیز وجود دارد که ذخیره کردن آنها برای اهداف تجربه کاربری میتواند مفید باشد، ازجمله مواردی مانند ارائهدهنده گذرکلید، زمان ایجاد اطلاعات کاربری، و زمان آخرین استفاده. در طراحی میانای کاربر گذرکلید بیشتر بخوانید.
کد نمونه: ذخیره کردن اطلاعات اعتباری
ما در مثالهایمان از کتابخانه 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 {
aaguid,
credential,
credentialBackedUp
} = registrationInfo;
// Name the credential based on AAGUID
const name =
aaguid === undefined ||
aaguid === '00000000-0000-0000-0000-000000000000' ?
req.useragent?.platform : aaguids[aaguid]?.name;
const base64PublicKey = isoBase64URL.fromBuffer(credential.publicKey);
// Existing, signed-in user
const { user } = res.locals;
// Save the credential
await Credentials.update({
id: credential.id,
passkey_user_id: user.passkey_user_id,
publicKey: base64PublicKey,
name,
aaguid,
transports: credential.transports || response.response.transports,
backed_up: credentialBackedUp,
registered_at: new Date().getTime()
});
// 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(اگر کارخواه برنامه Android باشد) است. بهعنوان RP، خواندنclientDataJSONبه شما امکان میدهد به اطلاعاتی که مرورگر در زمان درخواستcreateدیده است دسترسی پیدا کنید.response.attestationObjectحاوی دو بخش اطلاعات است:attestationStatementکه مربوط نیست مگراینکه از گواهی استفاده کنید.authenticatorDataدادههایی است که ارائهدهنده گذرکلید میبیند. بهعنوان «طرف اعتمادکننده»، خواندنauthenticatorDataبه شما امکان میدهد به دادههایی که ارائهدهنده گذرکلید میبیند و در زمان درخواستcreateبرگردانده میشود دسترسی داشته باشید.
authenticatorDataحاوی اطلاعات ضروری درباره اعتبارنامه کلید عمومی است که با گذرکلید جدیداً ایجادشده مرتبط است:
- خود اعتبارنامه کلید عمومی و شناسه اعتبارنامه یکتا برای آن.
- شناسه «طرف محدود» مرتبط با اطلاعات کاربری.
- پرچمهایی که وضعیت کاربر را هنگام ایجاد گذرکلید توصیف میکنند: اینکه آیا کاربر واقعاً حضور داشته است یا نه، و اینکه آیا کاربر با موفقیت درستیسنجی شده است یا نه (به بررسی عمیق userVerification مراجعه کنید).
- AAGUID شناسهای برای ارائهدهنده گذرکلید، مثل «مدیر گذرواژه Google»، است. براساس AAGUID، میتوانید ارائهدهنده گذرکلید را شناسایی کنید و نام را در صفحه مدیریت گذرکلید نمایش دهید. (به تعیین ارائهدهنده گذرکلید با AAGUID مراجعه کنید)
اگرچه authenticatorData در attestationObject تودرتو است، اطلاعاتی که دربردارد برای پیادهسازی گذرکلید شما لازم است، چه از گواهی استفاده کنید یا نه. authenticatorData کدگذاری شده است و حاوی فیلدهایی است که در قالب دودویی کدگذاری شدهاند. کتابخانه سمت سرور شما معمولاً تجزیه و رمزگشایی را انجام میدهد. اگر از کتابخانه سمت سرور استفاده نمیکنید، از getAuthenticatorData() سمت کارخواه استفاده کنید تا در تجزیه و رمزگشایی سمت سرور صرفهجویی کنید.
پیوست: درستیسنجی پاسخ ثبتنام
در پشت صحنه، درستیسنجی پاسخ ثبت شامل بررسیهای زیر است:
- مطمئن شوید شناسه طرف متکی با سایت شما مطابقت داشته باشد.
- مطمئن شوید که مبدأ درخواست، مبدأ موردانتظار برای سایت شما (نشانی وب سایت اصلی، برنامه Android) باشد.
- اگر به درستیسنجی کاربر نیاز دارید، مطمئن شوید که پرچم درستیسنجی کاربر
authenticatorData.uvرویtrueتنظیم شده باشد. - پرچم حضور کاربر
authenticatorData.upمعمولاً بایدtrueباشد، اما اگر اعتبارنامه بهصورت مشروط ایجاد شده باشد، بایدfalseباشد. - بررسی کنید که کارخواه توانسته باشد چالشی را که به آن دادهاید ارائه دهد. اگر از گواهی استفاده نمیکنید، این بررسی اهمیتی ندارد. بااینحال، پیادهسازی این بررسی جزو روالهای مطلوب است: این کار تضمین میکند که اگر در آینده تصمیم به استفاده از گواهی بگیرید، کد شما آماده است.
- مطمئن شوید که شناسه اطلاعات کاربری هنوز برای هیچ کاربری ثبت نشده باشد.
- تأیید کنید که الگوریتم مورداستفاده ارائهدهنده گذرکلید برای ایجاد اطلاعات کاربری، الگوریتمی است که شما فهرست کردهاید (در هر فیلد
algازpublicKeyCredentialCreationOptions.pubKeyCredParams، که معمولاً در کتابخانه سمت سرور شما تعریف شده است و برای شما قابلمشاهده نیست). این کار تضمین میکند که کاربران فقط میتوانند با الگوریتمهایی که شما اجازه دادهاید ثبتنام کنند.
برای کسب اطلاعات بیشتر، کد منبع SimpleWebAuthn برای verifyRegistrationResponse را بررسی کنید یا به فهرست کامل درستیسنجیها در مشخصات مراجعه کنید.