Anında kullanıcı arayüzü moduyla hızlı oturum açma

Kimliği doğrulanmamış bir kullanıcı "Oturum aç" bağlantısını tıkladığında, ödeme işlemine başladığında, bir öğeyi favorilere kaydettiğinde veya bir makale ödeme duvarına ulaştığında özel bir oturum açma sayfasına yönlendirilmesi bağlam kaybına neden olur ve kullanıcıların ayrılma oranını artırır. Anında kullanıcı arayüzü modu (navigator.credentials.get() içinde uiMode: 'immediate'), cihazda yerel geçiş anahtarları (publicKey) veya kayıtlı şifreler (password: true) mevcut olduğunda hemen birleşik bir tarayıcı hesap seçici sunarak bağlamsal hızlı oturum açma işlemlerini etkinleştirir.

Her zaman tarayıcı kullanıcı arayüzünü gösteren ve cihazlar arası QR kodlarına veya harici güvenlik anahtarlarına geri dönen standart kalıcı WebAuthn istemlerinin aksine, Immediate UI modu, cihazda uygun kimlik bilgileri yoksa NotAllowedError ile hızlı bir şekilde başarısız olur. Bu sayede uygulamanız, geri gelen kullanıcıların kimliğini tek bir dokunuşla yerinde doğrulayabilir. Yerel kimlik bilgileri olmayan kullanıcılar ise standart oturum açma sayfanıza, giriş yapmadan ödeme seçeneğine veya satır içi giriş formuna sorunsuz bir şekilde yönlendirilir.

Kullanıcı tarafından başlatılan bir etkinlik işleyicide (geçici kullanıcı etkinleştirmesi gerektirir) mevcut sayfadan ayrılmadan hemen önce, kullanıcının niyetinin net olduğu anlarda Anında Kullanıcı Arayüzü modunu tetikleyin. Anında kimlik doğrulama başarılı olursa kullanıcının amacını hemen tamamlayın (mevcut sayfayı yerinde güncelleyerek veya doğrudan kimliği doğrulanmış hedefine giderek). NotAllowedError ile başarısız olursa oturum açma veya kayıt akışı göstererek kullanıcının amacını tamamlamasını sağlayın.

Olası kullanım alanları:

  • Üstbilgi oturum açma bağlantısı veya düğme müdahalesi: Geri gelen kullanıcıların kimliğini hemen doğrulamak için genel "Oturum aç" bağlantısının (event.preventDefault()) tıklanmasına müdahale edin veya NotAllowedError adresindeki özel oturum açma sayfasına gitmeye devam edin.
  • Ödeme öncesinde dinamik oturum açma: Geri gelen müşteriler "Ödeme adımına geç"i tıkladığında tek dokunuşla kimliklerini doğrulayın. Böylece doğrudan kargo ve ödeme adımına yönlendirilirler veya yedek olarak standart oturum açma ya da giriş yapmadan ödeme ekranına geçerler.
  • Mikro işlemler için satır içi oturum açma (İşaretle, Beğen, Yer İşareti Koy, Kaydet): Oturumu kapalı bir kullanıcı mikro işlem düğmesini tıkladığında yerinde kimlik doğrulama yapın ve işlemi hemen tamamlayın (ör. aria-pressed="true" düğmesini açma) veya yedek bir giriş modülü ya da dönüş URL'si parametresi içeren bir oturum açma sayfası açın.
  • Makale içi esnek ödeme duvarları: Okuyucular, ödeme duvarı banner'ında "Hesabınızla okumaya devam edin"i tıkladığında veya yedek olarak satır içi oturum açma ya da kayıt formunu genişlettiğinde kimliklerini doğrulayın.

Kaçınılması gereken anti-pattern'ler:

  • Özel bir oturum açma sayfasında anında kullanıcı arayüzü modunu KULLANMAYIN: Nihai hedef oturum açma sayfanızdaki birincil "Oturum aç" veya "Geçiş anahtarıyla oturum aç" düğmesine hiçbir zaman uiMode: 'immediate' eklemeyin. Kullanıcılar, özellikle mevcut cihazda yerel kimlik bilgileri olmadığında veya cihazlar arası geçiş anahtarı (telefonla QR kodu tarama) ya da harici donanım güvenlik anahtarı kullanmak istediklerinde özel oturum açma sayfasına ulaşır. uiMode: 'immediate', cihazlar arası QR kodu ve güvenlik anahtarı seçeneklerini engellediği ve yerel kimlik bilgisi olmadığında hemen NotAllowedError hatası verdiği için özel bir oturum açma sayfasında kullanıldığında kullanıcıyı çıkmaz sokağa sokar ve düğmeyi tıklamak hiçbir işe yaramaz. Özel bir oturum açma sayfasında, standart bir kalıcı navigator.credentials.get({ publicKey }) düğmesiyle eşlenen form girişlerinde koşullu kullanıcı arayüzü (autocomplete="username webauthn" ile mediation: 'conditional') kullanın (Geçiş Anahtarı Kimlik Doğrulaması bölümüne bakın).
  • Yeniden kimlik doğrulama için Immediate UI modunu KULLANMAYIN: Hassas işlemlerden (ör. şifre değiştirme, kargo adresi güncelleme, kayıtlı ödeme yöntemlerini düzenleme veya yeni geçiş anahtarı ekleme) önce oturum açmış bir kullanıcının kimliğini yeniden doğrulamak için hiçbir zaman uiMode: 'immediate' kullanmayın. Yeniden kimlik doğrulama, boş olmayan bir allowCredentials listesi aracılığıyla istemi oturum açmış kullanıcının kimlik bilgileriyle kısıtlamalıdır. Bu liste, Immediate UI modu tarafından NotAllowedError ile reddedilir ve allowCredentials parametresinin atlanması, kullanıcının yanlışlıkla oturum açmış olduğu hesaptan farklı bir hesap seçmesine neden olur. Kademeli yeniden kimlik doğrulama için Geçiş Anahtarı ile Yeniden Kimlik Doğrulama başlıklı makaledeki adımları uygulayın.

Gizlilik ve güvenlik kısıtlamaları

uiMode: 'immediate' istendiğinde tarayıcılar katı gizlilik ve güvenlik kuralları uygular:

  • Geçici kullanıcı etkinliği gerekir: Sessiz kimlik bilgisi araştırmasını önlemek için kullanıcı tarafından başlatılan bir etkinlik işleyicide (ör. <button> veya <a> öğesindeki click işleyici) navigator.credentials.get({ uiMode: 'immediate' }) yöntemini çağırmanız GEREKİR. Sayfa yüklenirken (DOMContentLoaded) hiçbir zaman otomatik olarak çağrılmamalıdır. API'yi çağırmak, geçici etkinleştirmeyi tüketmez.
  • Boş olmayan bir allowCredentials listesi iletmeyin: uiMode: 'immediate' belirtildiğinde publicKey.allowCredentials değerini doldurmayın. Tarayıcılar, boş olmayan bir allowCredentials listesi içeren tüm anında kullanıcı arayüzü modu isteklerini NotAllowedError hatası vererek hemen reddeder. Böylece, güvenen taraflar önceki ziyaretleri tahmin etmek veya kullanıcıları oturumlar arasında izlemek için belirli kimlik bilgilerini araştıramaz.
  • AbortSignal (signal) iletmeyin: uiMode: 'immediate' belirtildiğinde navigator.credentials.get() üzerinde signal özelliğini AYARLAMAYIN. Tarayıcılar, anında giriş iletişim kutusunun programatik olarak kapatılmasına izin vermez. Sayfanızda arka planda koşullu kullanıcı arayüzü (mediation: 'conditional') isteği de çalıştırılıyorsa anında kullanıcı arayüzü modunu çağırmadan önce bu koşullu kullanıcı arayüzü AbortController için abort() yöntemini çağırın ve anında isteğin kendisinden signal yöntemini çıkarın.
  • Gizli ve özel tarama oturumları: Gizli veya özel tarama pencerelerinde yapılan istekler her zaman NotAllowedError ile reddedilir. Böylece özel oturumlar, yerel kimlik bilgileri olmayan oturumlardan ayırt edilemez.

Sunucu tarafı kurulumu

Seçenek oluşturma uç noktası

Kimliği doğrulanmamış ziyaretçiler için keşfedilebilir WebAuthn istek seçenekleri oluşturan bir uç nokta kullanıma sunun:

  1. Oturuma bağlı bir sorgulama oluşturun: Kriptografik olarak rastgele bir sorgulama arabelleği oluşturun, bunu sunucu oturumunda depolayın ve Base64URL olarak kodlayın.
  2. Kullanıcı doğrulama tercihini kalıcı hale getirme: userVerification: 'preferred' değerini ayarlayın ve oturumda expectedUserVerification: 'preferred' değerini depolayın.
  3. allowCredentials'i atlayın: Tarayıcılar NotAllowedError ile boş olmayan izin verilenler listelerini reddettiğinden, anında kullanıcı arayüzü modu seçeneklerine boş olmayan bir allowCredentials listesi eklemeyin.
// 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);
});

Çift kimlik bilgisi doğrulama uç noktaları

navigator.credentials.get({ password: true, publicKey, uiMode: 'immediate' }), geçiş anahtarı (PublicKeyCredential) veya kayıtlı şifre (PasswordCredential) ile çözümlenebileceğinden her iki kimlik bilgisi türü için de doğrulama işleyicileri sağlayın:

  1. WebAuthn onaylama doğrulaması (/api/login/verify): Onaylamayı, oturum sorgulamasına ve depolanan ortak anahtara göre doğrulayın. Kimlik bilgisi kimliği veritabanında bulunamazsa HTTP durumunu 404 olarak döndürün. Böylece istemci, PublicKeyCredential.signalUnknownCredential() aracılığıyla şifre yöneticisini bilgilendirebilir.
  2. Şifre doğrulama (/api/login/password): username (cred.id) ve password (cred.password) bilgilerini kullanıcı veritabanıyla karşılaştırarak doğrulayın ve kimliği doğrulanmış bir oturum oluşturun.

İstemci tarafı uygulama

Birleştirilmiş kimlik bilgisi isteği

  1. Hem geçiş anahtarlarını hem de şifreleri isteyin: PublicKeyCredential.parseRequestOptionsFromJSON() kullanarak sunucu seçeneklerinin kodunu çözün ve kullanıcı tarafından başlatılan bir tıklama işleyicisinde password: true, publicKey ve uiMode: 'immediate' ile navigator.credentials.get()'yi çağırın (signal'yi iletmeden).
  2. cred.type tarafından şube doğrulaması:
    • cred.type === 'public-key' ise onayı cred.toJSON() ile serileştirin ve WebAuthn doğrulama uç noktanıza POST edin. Sunucu HTTP 404 ile yanıt verdiğinde PublicKeyCredential.signalUnknownCredential({ rpId: publicKey.rpId, credentialId: encoded.id }) yöntemini çağırın.
    • cred.type === 'password' ise şifre doğrulama uç noktanıza POST { username: cred.id, password: cred.password } gönderin.
  3. Yerel kimlik bilgisi olmadığında NotAllowedError işlemini gerçekleştirme: NotAllowedError işlemini yakalayın (cihazda yerel geçiş anahtarı veya kayıtlı şifre olmadığında ya da kullanıcı seçiciyi kapattığında hemen tetiklenir) ve standart oturum açma akışınıza geçin (ör. satır içi oturum açma iletişim kutusu açma veya dönüş URL'si ile /signin sayfasına gitme).
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;
  }
});

Yedek stratejiler

Anında kullanıcı arayüzü modu yedeği

  • Progresif geliştirme deneyimi: uiMode: 'immediate''yi progresif geliştirme olarak değerlendirin. Bir tarayıcının adından veya sürümünden destek çıkarımı yapmayın. Tek güvenilir destek sinyali, PublicKeyCredential.getClientCapabilities() aracılığıyla yapılan capabilities.immediateGet çalışma zamanı kontrolüdür. ZORUNLU: navigator.credentials.get({ uiMode: 'immediate' }) yöntemini çağırmadan önce her zaman capabilities.immediateGet özelliğini kontrol edin. uiMode: 'immediate' özelliğini tanımayan tarayıcılar, bilinmeyen sözlük anahtarını yoksayar ve engelleyici bir kalıcı geçiş anahtarı ve QR kodu iletişim kutusu açar. capabilities.immediateGet yanlış olduğunda veya navigator.credentials.get(), NotAllowedError ile reddettiğinde uygulamanızın standart oturum açma deneyimine (ör. satır içi oturum açma modülü açma veya dönüş URL'si parametresiyle özel oturum açma sayfasına gitme) geri dönün. Böylece, desteklenmeyen tarayıcılardaki kullanıcılar mevcut oturum açma akışınızı değişmeden görür:
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' })
}

İstemci özellikleri algılama yedeği

'webauthn-polyfills' öğesini her zaman yükleyin ve istemci modülünüze aktarın. Böylece PublicKeyCredential desteklendiğinde PublicKeyCredential.getClientCapabilities() kullanılabilir:

import 'webauthn-polyfills';

Şifre kimlik bilgileri yedeği

navigator.credentials.get() içinde publicKey ile birlikte password: true parametresinin iletilmesi, destekleyen tarayıcıların, tarayıcının şifre yöneticisindeki kayıtlı şifreleri birleştirilmiş anında hesap seçiciye dahil etmesine olanak tanır.

  • Yedek deneyim: PasswordCredential özelliğini desteklemeyen tarayıcılar, password: true seçeneğini yoksayar veya capabilities.immediateGet özellik kontrolü aracılığıyla standart oturum açma formuna geri döner. Bu formda <input autocomplete="username webauthn"> ve <input type="password" autocomplete="current-password">, yerel şifre otomatik doldurma özelliği sağlar.

Kolay JSON serileştirme yedeği

'webauthn-polyfills' öğesini her zaman yükleyin ve istemci paketine aktarın. Böylece PublicKeyCredential.parseRequestOptionsFromJSON() ve PublicKeyCredential.prototype.toJSON(), PublicKeyCredential öğesini destekleyen tarayıcılarda tutarlı bir şekilde çalışır:

import 'webauthn-polyfills';

Signal API senkronizasyonu yedek seçeneği

WebAuthn Signal API (webauthn-signals), sunucu HTTP 404 ile yanıt verdiğinde kullanıcının şifre yöneticisinden eski geçiş anahtarlarını kaldıran bir progresif geliştirmedir.

  • Yedek deneyim: Aramaları if (PublicKeyCredential.signalUnknownCredential) ile engelleyin. Desteklenmediğinde, sinyal çağrısını düzgün bir şekilde atlayın ve kullanıcıyı alternatif bir yöntemle oturum açmaya yönlendiren net bir mesaj gösterin.