هدایت کردن کاربر احراز هویتنشده به صفحه ورود به سیستم اختصاصی وقتی روی پیوند «ورود به سیستم» کلیک میکند، تسویهحساب را شروع میکند، موردی را در موارد دلخواه ذخیره میکند، یا به دیوار پرداخت مقاله میرسد باعث ازدست رفتن بافت و افزایش ریزش کاربر میشود. حالت میانای کاربری فوری (uiMode: 'immediate' در navigator.credentials.get()) با ارائه فوری انتخابگر حساب مرورگر یکپارچه وقتی گذرکلیدهای محلی (publicKey) یا گذرواژههای ذخیرهشده (password: true) در دستگاه وجود دارد، ورودهای سریع به سیستم بافتی را فعال میکند.
برخلاف پیاموارههای WebAuthn استاندارد—که همیشه واسط کاربر مرورگر را نمایش میدهند و به رمزینههای پاسخسریع بیندستگاهی یا کلیدهای امنیتی خارجی برمیگردند—حالت «واسط کاربر فوری» اگر هیچ اطلاعات کاربری واجدشرایطی بهصورت محلی در دستگاه دردسترس نباشد، بهسرعت با NotAllowedError ناموفق میشود. این ویژگی به برنامه شما امکان میدهد کاربران بازگشتی را در همانجا با یک تکضرب اصالتسنجی کند و درعینحال کاربران فاقد اطلاعات کاربری محلی را بهطور یکپارچه به صفحه ورود به سیستم استاندارد، تسویهحساب بهعنوان مهمان، یا فرم ورود به سیستم درونخطی شما منتقل کند.
الگوها و ضدالگوهای UX توصیهشده
حالت «واسط کاربر فوری» را در داخل یک گرداننده رویداد آغازشده توسط کاربر (که نیاز به فعالسازی موقت کاربر دارد) در لحظاتی که قصد کاربر واضح است، درست قبلاز خروج از صفحه فعلی، راهاندازی کنید. اگر اصالتسنجی فوری موفقیتآمیز بود، قصد کاربر را فوراً تکمیل کنید (چه با بهروزرسانی صفحه فعلی در محل یا رفتن مستقیم به مقصد اصالتسنجیشده). اگر با NotAllowedError ناموفق بود، جریان ورود به سیستم یا ثبتنام را با مسیری برای تکمیل هدف کاربر در تکمیل نشان دهید.
موارد استفاده بالقوه:
- رهگیری دکمه یا پیوند ورود به سیستم در سرصفحه: رهگیری کلیک روی پیوند سراسری «ورود به سیستم» (
event.preventDefault()) برای اصالتسنجی فوری کاربران بازگشتی، یا ادامه پیمایش به صفحه ورود به سیستم اختصاصی درNotAllowedError. - ورود به سیستم پویا قبلاز تسویهحساب: خریداران بازگشتی را با یک تکضرب هنگام کلیک روی «ادامه به تسویهحساب» اصالتسنجی کنید تا مستقیماً به مرحله ارسال و پرداخت بروند، یا درصورت عدم موفقیت، به صفحه ورود به سیستم استاندارد یا تسویهحساب بهعنوان مهمان منتقل شوند.
- ورود به سیستم درونخطی برای کنشهای کوچک (افزودن به موارد دلخواه، پسندیدن، نشانکگذاری، ذخیره کردن): وقتی کاربر خارجشده از سیستم روی دکمه کنش کوچک کلیک میکند، اصالتسنجی را در همانجا انجام دهید و بلافاصله کنش را تکمیل کنید (برای مثال، تغییر وضعیت
aria-pressed="true")، یا یک مدال ورود به سیستم جایگزین یا صفحه ورود به سیستم با پارامتر نشانی وب بازگشت باز کنید. - دیوار نسبتاً پولی درون مقاله: وقتی خوانندگان روی برنمای دیوار پولی «ادامه خواندن با حساب شما» کلیک میکنند، اصالت آنها را در همانجا تأیید میکند یا درصورت عدم موفقیت، فرم ورود به سیستم یا ثبتنام درونخطی را گسترش میدهد.
الگوهای منفی که باید از آنها اجتناب کرد:
- از حالت «رابط کاربری فوری» در صفحه ورود به سیستم اختصاصی استفاده نکنید: هرگز
uiMode: 'immediate'را به دکمه اصلی «ورود به سیستم» یا «ورود به سیستم با گذرکلید» در صفحه ورود به سیستم مقصد نهایی پیوست نکنید. کاربران بهطور خاص زمانی به صفحه ورود به سیستم اختصاصی میرسند که هیچ اطلاعات اعتباری محلی در دستگاه فعلی نداشته باشند، یا زمانی که قصد داشته باشند از گذرکلید بیندستگاهی (اسکن کردن رمزینه پاسخسریع با تلفن) یا کلید امنیتی سختافزاری خارجی استفاده کنند. چونuiMode: 'immediate'گزینههای رمزینه پاسخسریع و کلید امنیتی بیندستگاهی را سرکوب میکند و وقتی اعتبارنامه محلی وجود نداشته باشد فوراًNotAllowedErrorرا برمیگرداند، استفاده از آن در صفحه ورود به سیستم اختصاصی کاربر را در بنبستی گرفتار میکند که در آن کلیک کردن روی دکمه هیچ کاری انجام نمیدهد. در صفحه ورود به سیستم اختصاصی، از «رابط کاربری شرطی» (mediation: 'conditional'باautocomplete="username webauthn") در ورودیهای فرم جفتشده با دکمهnavigator.credentials.get({ publicKey })استاندارد (به اصالتسنجی گذرکلید مراجعه کنید) استفاده کنید. - از حالت «رابط کاربری فوری» برای بازدرستیسنجی گامبهگام استفاده نکنید: هرگز از
uiMode: 'immediate'برای بازدرستیسنجی کاربری که قبلاً به سیستم وارد شده است قبلاز انجام عملیات حساس (مانند تغییر گذرواژه، بهروزرسانی نشانی ارسال کالا، ویرایش روشهای پرداخت ذخیرهشده، یا افزودن گذرکلید جدید) استفاده نکنید. درستیسنجی مجدد باید پیامواره را ازطریق فهرست غیرخالیallowCredentialsبه اطلاعات کاربری کاربر واردشده به سیستم محدود کند، که حالت «رابط کاربری فوری» آن را باNotAllowedErrorرد میکند، و حذفallowCredentialsبه کاربر اجازه میدهد بهطور ناخواسته حسابی متفاوت از حسابی را که درحالحاضر به سیستم آن وارد شده است انتخاب کند. برای بازدرستیسنجی ارتقایی، بازدرستیسنجی گذرکلید را دنبال کنید.
محدودیتهای حریم خصوصی و امنیت
مرورگرها هنگام درخواست uiMode: 'immediate'، قوانین سختگیرانهای را برای حریم خصوصی و امنیت اعمال میکنند:
- فعالسازی گذرا توسط کاربر الزامی است: برای جلوگیری از کاوش بیصدای اطلاعات کاربری، باید
navigator.credentials.get({ uiMode: 'immediate' })را در یک کنترلکننده رویداد آغازشده توسط کاربر (مانند شنوندهclickدر عنصر<button>یا<a>) فراخوانی کنید. هرگز آن را بهطور خودکار هنگام بار کردن صفحه (DOMContentLoaded) فراخوانی نکنید. فراخوانی کردن API، فعالسازی گذرا را مصرف نمیکند. - فهرست
allowCredentialsغیرخالی را ارسال نکنید: وقتیuiMode: 'immediate'مشخص شده است،publicKey.allowCredentialsرا پر نکنید. مرورگرها بلافاصله هرگونه درخواست حالت «رابط کاربری فوری» را که فهرستallowCredentialsغیرخالی داشته باشد با ایجادNotAllowedErrorرد میکنند تا طرفهای متکی نتوانند شناسههای اطلاعات اعتباری خاص را برای استنباط بازدیدهای قبلی یا ردیابی کاربران در جلسهها کاوش کنند. AbortSignal(signal) را ارسال نکنید: وقتیuiMode: 'immediate'مشخص شده است، ویژگیsignalرا درnavigator.credentials.get()تنظیم نکنید، زیرا مرورگرها رد کردن برنامهای کادر گفتگوی ورود فوری را مجاز نمیدانند. اگر صفحهتان درخواست «واسط کاربر شرطی» (mediation: 'conditional') پسزمینهای را نیز اجرا میکند، قبلاز فراخوانی حالت «واسط کاربر فوری»،abort()را در آن «واسط کاربر شرطی»AbortControllerفراخوانی کنید وsignalرا از خود درخواست فوری حذف کنید.- جلسههای مرور خصوصی و ناشناس: درخواستهای ایجادشده در پنجرههای مرور خصوصی یا ناشناس همیشه با
NotAllowedErrorرد میشوند تا جلسههای خصوصی از جلسههای بدون اطلاعات کاربری محلی قابلتشخیص نباشند.
راهاندازی سمت سرور
نقطه پایان تولید گزینهها
نقطه پایانیای را افشا کنید که گزینههای درخواست WebAuthn قابلشناسایی را برای بازدیدکنندگان احراز هویتنشده تولید میکند:
- تولید چالش محدود به جلسه: یک میانگیر چالش تصادفی رمزنگاریشده ایجاد کنید، آن را در جلسه سرور ذخیره کنید، و آن را بهعنوان Base64URL کدبندی کنید.
- Persist user verification preference: Set
userVerification: 'preferred'and storeexpectedUserVerification: 'preferred'in the session. - حذف
allowCredentials: فهرست غیرخالیallowCredentialsرا در گزینههای حالت «واسط کاربر فوری» قرار ندهید، زیرا مرورگرها فهرستهای مجاز غیرخالی را باNotAllowedErrorرد میکنند.
// Server-side options generation example (/api/login/options)
router.post('/api/login/options', (req, res) => {
const options = {
challenge: serverGeneratedBase64UrlChallenge, // High-entropy random challenge stored in session
rpId: 'example.com',
userVerification: 'preferred',
};
req.session.challenge = options.challenge;
req.session.expectedUserVerification = 'preferred';
return res.json(options);
});
نقاط پایانی درستیسنجی اعتبارنامه دوگانه
چون navigator.credentials.get({ password: true, publicKey, uiMode: 'immediate' }) میتواند با گذرکلید (PublicKeyCredential) یا گذرواژه ذخیرهشده (PasswordCredential) حل شود، برای هر دو نوع اعتبارنامه، گرداننده درستیسنجی ارائه دهید:
- درستیسنجی ادعای WebAuthn (
/api/login/verify): ادعا را دربرابر چالش جلسه و کلید عمومی ذخیرهشده درستیسنجی کنید. اگر شناسه اطلاعات کاربری در پایگاه داده پیدا نشد، وضعیت HTTP404را برگردانید تا کارخواه بتواند ازطریقPublicKeyCredential.signalUnknownCredential()به مدیر گذرواژه اطلاع دهد. - درستیسنجی گذرواژه (
/api/login/password):username(cred.id) وpassword(cred.password) را درمقابل پایگاه داده کاربر درستیسنجی کنید و جلسه اصالتسنجیشدهای برقرار کنید.
پیادهسازی سمت کارخواه
درخواست اطلاعات اعتباری یکپارچه
- درخواست گذرکلید و گذرواژه: گزینههای سرور را بااستفاده از
PublicKeyCredential.parseRequestOptionsFromJSON()رمزگشایی کنید وnavigator.credentials.get()را در گرداننده کلیک آغازشده توسط کاربر باpassword: true،publicKey، وuiMode: 'immediate'(بدون ارسالsignal) فراخوانی کنید. - درستیسنجی شعبه توسط
cred.type:- اگر
cred.type === 'public-key'، ادعا را باcred.toJSON()سریالسازی کنید و آن را به نقطه پایان درستیسنجی «اصالتسنجی وب» خود ارسال کنید. وقتی سرور با HTTP404پاسخ میدهد، باPublicKeyCredential.signalUnknownCredential({ rpId: publicKey.rpId, credentialId: encoded.id })تماس بگیرید. - اگر
cred.type === 'password'،{ username: cred.id, password: cred.password }را به نقطه پایانی درستیسنجی گذرواژه خود ارسال کنید.
- اگر
- مدیریت
NotAllowedErrorوقتی اعتبارنامه محلی وجود ندارد:NotAllowedErrorرا بگیرید (بلافاصله وقتی گذرکلید محلی یا گذرواژه ذخیرهشدهای در دستگاه وجود ندارد یا وقتی کاربر انتخابکننده را رد میکند، ایجاد میشود) و به جریان ورود به سیستم استاندارد خود بروید (مانند باز کردن کادر گفتگوی ورود به سیستم درونخطی یا پیمایش به/signinبا نشانی وب بازگشت).
let isAuthenticating = false;
async function authenticateWithImmediateUi() {
const optionsResponse = await fetch('/api/login/options', { method: 'POST' });
if (!optionsResponse.ok) {
throw new Error('Failed to fetch authentication options');
}
const optionsJSON = await optionsResponse.json();
const publicKey = PublicKeyCredential.parseRequestOptionsFromJSON(optionsJSON);
// MANDATORY: Request both password and publicKey with uiMode: 'immediate' and NO signal parameter
const cred = await navigator.credentials.get({
password: true,
publicKey,
uiMode: 'immediate',
});
if (!cred) {
throw new DOMException('No credential returned', 'NotAllowedError');
}
if (cred.type === 'public-key') {
const encoded = cred.toJSON();
const verifyResponse = await fetch('/api/login/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(encoded),
});
if (verifyResponse.status === 404) {
await PublicKeyCredential.signalUnknownCredential({
rpId: publicKey.rpId || window.location.hostname,
credentialId: encoded.id, // Base64URL-encoded credential ID string
});
throw new Error('Passkey credential is no longer recognized by the server');
}
if (!verifyResponse.ok) {
throw new Error('Passkey verification failed');
}
return await verifyResponse.json();
}
if (cred.type === 'password') {
const verifyResponse = await fetch('/api/login/password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: cred.id,
password: cred.password,
}),
});
if (!verifyResponse.ok) {
throw new Error('Password verification failed');
}
return await verifyResponse.json();
}
throw new Error(`Unsupported credential type: ${cred.type}`);
}
// Example: Intercepting an inline micro-action (Favorite button) or Header Sign-in click
const favoriteButton = document.getElementById('favorite-btn');
favoriteButton.addEventListener('click', async (event) => {
event.preventDefault();
if (isAuthenticating) return;
// If already signed in, toggle the micro-action state immediately
if (document.body.dataset.signedIn === 'true') {
const nextPressed = favoriteButton.getAttribute('aria-pressed') !== 'true';
favoriteButton.setAttribute('aria-pressed', String(nextPressed));
return;
}
isAuthenticating = true;
try {
const user = await authenticateWithImmediateUi();
// Complete the user's intended action immediately
document.body.dataset.signedIn = 'true';
favoriteButton.setAttribute('aria-pressed', 'true');
updateHeaderUserState(user);
} catch (err) {
if (err.name === 'NotAllowedError') {
// No local credentials available or user dismissed dialog:
// transition smoothly to fallback sign-in UI or redirect with return URL
showFallbackSignInUi({ returnAction: 'favorite' });
return;
}
console.error('Authentication error:', err);
} finally {
isAuthenticating = false;
}
});
راهبردهای جایگزین
بازگشت فوری به حالت واسط کاربر
- تجربه ارتقای پیشرو: با
uiMode: 'immediate'بهعنوان ارتقای پیشرو رفتار کنید. پشتیبانی را از نام یا نسخه مرورگر استنباط نکنید؛ تنها سیگنال پشتیبانی قابلاعتماد، بررسی زمان اجرایcapabilities.immediateGetازطریقPublicKeyCredential.getClientCapabilities()است. اجباری: همیشه قبلاز فراخوانیnavigator.credentials.get({ uiMode: 'immediate' })،capabilities.immediateGetرا بررسی کنید—مرورگرهایی کهuiMode: 'immediate'را تشخیص نمیدهند، کلید واژهنامه ناشناخته را نادیده میگیرند و یک کادر گفتگوی رمزینه پاسخسریع و گذرکلید مسدودکننده را باز میکنند. وقتیcapabilities.immediateGetنادرست است، یا وقتیnavigator.credentials.get()باNotAllowedErrorرد میشود، به تجربه ورود به سیستم استاندارد برنامهتان برگردید (مانند باز کردن یک مدال ورود به سیستم درونخطی یا پیمایش به صفحه ورود به سیستم اختصاصی با پارامتر نشانی وب بازگشت)، تا کاربران در مرورگرهای پشتیبانینشده جریان ورود به سیستم موجود شما را بدون تغییر ببینند:
async function isImmediateUiSupported() {
if (!window.PublicKeyCredential || !PublicKeyCredential.getClientCapabilities) {
return false;
}
try {
const capabilities = await PublicKeyCredential.getClientCapabilities();
return Boolean(capabilities?.immediateGet);
} catch {
return false;
}
}
async function authenticateWithImmediateUi() {
// MANDATORY: Check immediateGet support so unsupported browsers do not open a modal QR dialog
const supported = await isImmediateUiSupported();
if (!supported) {
// Throw NotAllowedError so the caller's single catch path treats "unsupported" like "no local credentials"
throw new DOMException('Immediate UI mode is not supported', 'NotAllowedError');
}
// Proceed with fetching options and calling navigator.credentials.get({ password: true, publicKey, uiMode: 'immediate' })
}
جایگزین تشخیص قابلیتهای کارخواه
همیشه 'webauthn-polyfills' را نصب کنید و آن را در ماژول کارخواهتان وارد کنید تا هر زمان که PublicKeyCredential پشتیبانی شد، PublicKeyCredential.getClientCapabilities() دردسترس باشد:
import 'webauthn-polyfills';
جایگزین اعتبارنامه گذرواژه
ارسال password: true در کنار publicKey در navigator.credentials.get() به مرورگرهای پشتیبانیکننده اجازه میدهد گذرواژههای ذخیرهشده را از مدیر گذرواژه مرورگر در انتخابگر حساب فوری یکپارچه اضافه کنند.
- تجربه جایگزین: مرورگرهایی که از
PasswordCredentialپشتیبانی نمیکنند، گزینهpassword: trueرا نادیده میگیرند یا ازطریق بررسی ویژگیcapabilities.immediateGetبه فرم ورود به سیستم استاندارد برمیگردند، جایی که<input autocomplete="username webauthn">و<input type="password" autocomplete="current-password">تکمیل خودکار گذرواژه بومی را ارائه میدهند.
جایگزین آسان برای سریالسازی JSON
همیشه 'webauthn-polyfills' را نصب کنید و آن را در بسته مشتری وارد کنید تا PublicKeyCredential.parseRequestOptionsFromJSON() و PublicKeyCredential.prototype.toJSON() بهطور یکسان در مرورگرهایی که از PublicKeyCredential پشتیبانی میکنند کار کنند:
import 'webauthn-polyfills';
جایگزین همگامسازی Signal API
WebAuthn Signal API (webauthn-signals) یک ارتقای پیشرو است که وقتی سرور با HTTP 404 پاسخ میدهد، گذرکلیدهای قدیمی را از مدیر گذرواژه کاربر حذف میکند.
- تجربه جایگزین: تماسهای دروازه پشت
if (PublicKeyCredential.signalUnknownCredential). وقتی پشتیبانی نمیشود، تماس سیگنال را بهطور مناسب رد کنید و پیام واضحی نمایش دهید که کاربر را برای ورود به سیستم با روشی جایگزین راهنمایی کند.