जब कोई उपयोगकर्ता "साइन इन करें" लिंक पर क्लिक करता है, चेकआउट शुरू करता है, किसी आइटम को पसंदीदा के तौर पर सेव करता है या किसी लेख के पेवॉल पर पहुंचता है, तो उसे साइन इन करने के लिए एक अलग पेज पर रीडायरेक्ट किया जाता है. इससे उपयोगकर्ता का कॉन्टेक्स्ट खो जाता है और वह वेबसाइट छोड़ देता है. इमीडिएट यूज़र इंटरफ़ेस (यूआई) मोड (uiMode: 'immediate' में navigator.credentials.get()) की मदद से, उपयोगकर्ता तुरंत साइन इन कर सकते हैं. इसके लिए, डिवाइस पर स्थानीय पासकी (publicKey) या सेव किए गए पासवर्ड (password: true) मौजूद होने पर, ब्राउज़र में खाता चुनने की सुविधा तुरंत उपलब्ध कराई जाती है.
स्टैंडर्ड मॉडल WebAuthn प्रॉम्प्ट, हमेशा ब्राउज़र यूज़र इंटरफ़ेस (यूआई) दिखाते हैं. साथ ही, क्रॉस-डिवाइस क्यूआर कोड या बाहरी सुरक्षा कुंजियों पर वापस चले जाते हैं. हालांकि, Immediate UI मोड में ऐसा नहीं होता. अगर डिवाइस पर स्थानीय तौर पर कोई मान्य क्रेडेंशियल उपलब्ध नहीं है, तो यह मोड तुरंत NotAllowedError के साथ काम करना बंद कर देता है. इससे आपका ऐप्लिकेशन, लौटने वाले उपयोगकर्ताओं की पुष्टि एक टैप में कर सकता है. साथ ही, जिन उपयोगकर्ताओं के पास स्थानीय क्रेडेंशियल नहीं हैं उन्हें आपके स्टैंडर्ड साइन-इन पेज, लॉग इन किए बिना खरीदारी करने की सुविधा या इनलाइन लॉगिन फ़ॉर्म पर आसानी से रीडायरेक्ट कर सकता है.
UX के लिए सुझाए गए पैटर्न और एंटी-पैटर्न
उपयोगकर्ता के शुरू किए गए इवेंट हैंडलर में, Immediate UI मोड को ट्रिगर करें. इसके लिए, उपयोगकर्ता को कुछ समय के लिए चालू करना ज़रूरी है. ऐसा तब करें, जब उपयोगकर्ता का मकसद साफ़ तौर पर मौजूदा पेज से दूर जाने का हो. अगर उपयोगकर्ता की तुरंत पुष्टि हो जाती है, तो उसकी कार्रवाई को तुरंत पूरा करें. इसके लिए, मौजूदा पेज को अपडेट करें या सीधे उस पेज पर जाएं जिसकी पुष्टि की गई है. अगर NotAllowedError की वजह से यह काम नहीं करता है, तो साइन-इन या रजिस्ट्रेशन का फ़्लो दिखाएं. इसमें उपयोगकर्ता के मकसद को पूरा करने का पाथ भी शामिल होना चाहिए.
इस्तेमाल के संभावित उदाहरण:
- हेडर में मौजूद साइन-इन लिंक या बटन को इंटरसेप्ट करना: लौटने वाले उपयोगकर्ताओं की तुरंत पुष्टि करने के लिए, ग्लोबल "साइन इन करें" लिंक (
event.preventDefault()) पर क्लिक को इंटरसेप्ट करें. इसके अलावा,NotAllowedErrorपर मौजूद साइन-इन पेज पर जाएं. - चेकआउट करने से पहले डाइनैमिक साइन-इन: वापस आने वाले खरीदारों की पुष्टि करने के लिए, उन्हें "चेकआउट के लिए आगे बढ़ें" पर क्लिक करने के लिए कहें. इससे वे सीधे शिपिंग और पेमेंट वाले चरण पर पहुंच जाएंगे. इसके अलावा, वे स्टैंडर्ड साइन-इन या लॉग इन किए बिना खरीदारी करने वाली स्क्रीन पर भी जा सकते हैं.
- माइक्रो-ऐक्शन (पसंदीदा, पसंद करें, बुकमार्क करें, सेव करें) के लिए इनलाइन साइन-इन: जब साइन-आउट किया हुआ कोई उपयोगकर्ता किसी माइक्रो-ऐक्शन बटन पर क्लिक करे, तो उसी समय उसकी पुष्टि करें और कार्रवाई को तुरंत पूरा करें (उदाहरण के लिए,
aria-pressed="true"को टॉगल करना). इसके अलावा, फ़ॉलबैक लॉगिन मॉडल या साइन-इन पेज को रिटर्न यूआरएल पैरामीटर के साथ खोलें. - लेख में मौजूद सॉफ़्ट पेवॉल: जब लोग पेवॉल बैनर पर "अपने खाते से पढ़ना जारी रखें" पर क्लिक करें, तो उनकी पहचान की पुष्टि करें. इसके अलावा, फ़ॉलबैक पर इनलाइन साइन-इन या रजिस्ट्रेशन फ़ॉर्म को बड़ा करें.
इन एंटी-पैटर्न से बचें:
- साइन-इन करने के लिए बनाए गए पेज पर, Immediate UI मोड का इस्तेमाल न करें: साइन-इन करने के लिए बनाए गए पेज पर, मुख्य "साइन इन करें" या "पासकी से साइन इन करें" बटन के साथ
uiMode: 'immediate'को कभी अटैच न करें. उपयोगकर्ता, साइन-इन पेज पर खास तौर पर तब पहुंचते हैं, जब उनके मौजूदा डिवाइस पर कोई स्थानीय क्रेडेंशियल मौजूद न हो. इसके अलावा, जब उन्हें किसी क्रॉस-डिवाइस पासकी (फ़ोन से क्यूआर कोड स्कैन करना) या बाहरी हार्डवेयर सुरक्षा कुंजी का इस्तेमाल करना हो, तब भी वे इस पेज पर पहुंचते हैं.uiMode: 'immediate', क्रॉस-डिवाइस क्यूआर कोड और सुरक्षा कुंजी के विकल्पों को दबा देता है. साथ ही, कोई स्थानीय क्रेडेंशियल मौजूद न होने पर, तुरंतNotAllowedErrorदिखाता है. इसलिए, साइन-इन करने के लिए बने पेज पर इसका इस्तेमाल करने से, उपयोगकर्ता ऐसी स्थिति में फंस जाता है जहां बटन पर क्लिक करने से कुछ नहीं होता. साइन-इन करने के लिए बनाए गए पेज पर, फ़ॉर्म इनपुट के लिए कंडिशनल यूज़र इंटरफ़ेस (mediation: 'conditional'के साथautocomplete="username webauthn") का इस्तेमाल करें. साथ ही, स्टैंडर्ड मॉडलnavigator.credentials.get({ publicKey })बटन का इस्तेमाल करें. इसके बारे में ज़्यादा जानने के लिए, पासकी की मदद से पुष्टि करना लेख पढ़ें. - फिर से पुष्टि करने के लिए, Immediate यूज़र इंटरफ़ेस (यूआई) मोड का इस्तेमाल न करें: संवेदनशील कार्रवाइयां करने से पहले, पहले से साइन इन किए हुए उपयोगकर्ता की फिर से पुष्टि करने के लिए, कभी भी
uiMode: 'immediate'का इस्तेमाल न करें. जैसे, पासवर्ड बदलना, शिपिंग पता अपडेट करना, पेमेंट के सेव किए गए तरीकों में बदलाव करना या नई पासकी जोड़ना. फिर से पुष्टि करने की प्रोसेस के दौरान, साइन इन किए हुए उपयोगकर्ता के क्रेडेंशियल के लिए प्रॉम्प्ट को सीमित करना ज़रूरी है. इसके लिए, खाली न होने वालीallowCredentialsसूची का इस्तेमाल करें. हालांकि, इमीडिएट यूज़र इंटरफ़ेस (यूआई) मोड,NotAllowedErrorके साथ इसे अस्वीकार कर देता है. साथ ही,allowCredentialsको छोड़ने पर, उपयोगकर्ता गलती से उस खाते के बजाय किसी दूसरे खाते को चुन सकता है जिससे उसने फ़िलहाल साइन इन किया हुआ है. स्टेप-अप रीऑथेंटिकेशन के लिए, पासकी रीऑथेंटिकेशन का तरीका अपनाएं.
निजता और सुरक्षा से जुड़ी पाबंदियां
uiMode: 'immediate' का अनुरोध किए जाने पर, ब्राउज़र निजता और सुरक्षा से जुड़े सख्त नियम लागू करते हैं:
- उपयोगकर्ता गतिविधि कुछ समय के लिए ज़रूरी है: क्रेडेंशियल की जांच चुपचाप न की जा सके, इसके लिए आपको उपयोगकर्ता की ओर से शुरू किए गए इवेंट हैंडलर (जैसे,
<button>या<a>एलिमेंट परclickलिसनर) मेंnavigator.credentials.get({ uiMode: 'immediate' })को कॉल करना होगा. पेज लोड (DOMContentLoaded) होने पर, इसे कभी भी अपने-आप चालू न करें. एपीआई को कॉल करने से, कुछ समय के लिए चालू होने की सुविधा का इस्तेमाल नहीं होता. allowCredentialsकी ऐसी सूची पास न करें जो खाली न हो:uiMode: 'immediate'की वैल्यू तय होने पर,publicKey.allowCredentialsकी वैल्यू न डालें. ब्राउज़र, खाली न होने वालीallowCredentialsसूची के साथ, इमीडिएट यूज़र इंटरफ़ेस (यूआई) मोड के किसी भी अनुरोध को तुरंत अस्वीकार कर देते हैं. इसके लिए, वेNotAllowedErrorको थ्रो करते हैं. इससे, भरोसा करने वाली इकाइयां, क्रेडेंशियल के खास आईडी की जांच नहीं कर पाती हैं. इससे, वे पिछली विज़िट का अनुमान नहीं लगा पाती हैं या अलग-अलग सेशन में उपयोगकर्ताओं को ट्रैक नहीं कर पाती हैं.AbortSignal(signal) पास न करें:uiMode: 'immediate'तय होने पर,navigator.credentials.get()परsignalप्रॉपर्टी सेट न करें. ऐसा इसलिए, क्योंकि ब्राउज़र, तुरंत लॉगिन करने वाले डायलॉग को प्रोग्राम के हिसाब से खारिज करने की अनुमति नहीं देते. अगर आपका पेज बैकग्राउंड में कंडीशनल यूज़र इंटरफ़ेस (mediation: 'conditional') का अनुरोध भी चलाता है, तो इमीडिएट यूज़र इंटरफ़ेस मोड को शुरू करने से पहले, उस कंडीशनल यूज़र इंटरफ़ेसAbortControllerपरabort()को कॉल करें. साथ ही, इमीडिएट अनुरोध सेsignalको हटा दें.- गुप्त और निजी ब्राउज़िंग सेशन: गुप्त या निजी ब्राउज़िंग विंडो में किए गए अनुरोधों को हमेशा
NotAllowedErrorके साथ अस्वीकार कर दिया जाता है. इससे निजी सेशन और बिना लोकल क्रेडेंशियल वाले सेशन में अंतर नहीं किया जा सकता.
सर्वर-साइड सेटअप
विकल्प जनरेट करने वाला एंडपॉइंट
ऐसा एंडपॉइंट उपलब्ध कराएं जो बिना पुष्टि वाले विज़िटर के लिए, खोजे जा सकने वाले WebAuthn अनुरोध के विकल्प जनरेट करता हो:
- सेशन से जुड़ा चैलेंज जनरेट करना: क्रिप्टोग्राफ़ी के हिसाब से रैंडम चैलेंज बफ़र बनाएं, उसे सर्वर सेशन में सेव करें, और Base64URL के तौर पर कोड में बदलें.
- उपयोगकर्ता की पुष्टि करने की प्राथमिकता को सेव रखना:
userVerification: 'preferred'को सेट करें और सेशन मेंexpectedUserVerification: 'preferred'को सेव करें. allowCredentialsको शामिल न करें: Immediate UI मोड के विकल्पों में,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): सेशन चैलेंज और सेव किए गए सार्वजनिक पासकोड के आधार पर, दावे की पुष्टि करें. अगर क्रेडेंशियल आईडी, डेटाबेस में नहीं मिलता है, तो एचटीटीपी स्टेटस404दिखाएं. इससे क्लाइंट,PublicKeyCredential.signalUnknownCredential()के ज़रिए Password Manager को सूचना दे सकता है. - पासवर्ड की पुष्टि (
/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()के साथ असर्शन को सीरियलाइज़ करें और उसे अपने WebAuthn वेरिफिकेशन एंडपॉइंट पर पोस्ट करें. जब सर्वर एचटीटीपी404के साथ जवाब दे, तबPublicKeyCredential.signalUnknownCredential({ rpId: publicKey.rpId, credentialId: encoded.id })को कॉल करें. - अगर यह
cred.type === 'password'है, तो पासवर्ड की पुष्टि करने वाले एंडपॉइंट पर{ username: cred.id, password: cred.password }को POST करें.
- अगर
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'को परतदार वृद्धि के तौर पर इस्तेमाल करें. किसी ब्राउज़र के नाम या वर्शन से यह अनुमान न लगाएं कि वह काम करता है या नहीं. इसके लिए,PublicKeyCredential.getClientCapabilities()की मदद सेcapabilities.immediateGetरनटाइम की जांच करना ही सबसे सही तरीका है. ज़रूरी: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';
पासवर्ड क्रेडेंशियल फ़ॉलबैक
navigator.credentials.get() में publicKey के साथ password: true को पास करने पर, ब्राउज़र के पासवर्ड मैनेजर में सेव किए गए पासवर्ड, यूनिफ़ाइड इमीडिएट अकाउंट चूज़र में शामिल किए जा सकते हैं. हालांकि, ऐसा सिर्फ़ उन ब्राउज़र में किया जा सकता है जिनमें यह सुविधा काम करती है.
- फ़ॉलबैक अनुभव:
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) एक प्रोग्रेसिव एन्हांसमेंट है. यह उपयोगकर्ता के पासवर्ड मैनेजर से पुरानी पासकी हटा देता है. ऐसा तब होता है, जब सर्वर एचटीटीपी 404 के साथ जवाब देता है.
- फ़ॉलबैक अनुभव:
if (PublicKeyCredential.signalUnknownCredential)के पीछे गेट कॉल. अगर यह सुविधा काम नहीं करती है, तो सिग्नल कॉल को स्किप करें और उपयोगकर्ता को साफ़ तौर पर मैसेज दिखाकर बताएं कि वह किसी दूसरे तरीके से साइन इन करे.