Geçiş Anahtarı ile Kimlik Doğrulama

Bu kılavuzda, hem açık düğme tetikleyicileri hem de sorunsuz tarayıcı otomatik doldurma önerileri (koşullu kullanıcı arayüzü) aracılığıyla keşfedilebilir kimlik bilgilerini kullanarak geri gelen kullanıcı kimlik doğrulamasının nasıl uygulanacağı ayrıntılı olarak açıklanmaktadır.

Sunucu Tarafı

Seçenek oluşturma

Standartlara uygun şekilde incelenmiş bir kitaplık kullanarak WebAuthn istek parametreleri oluşturan bir uç nokta oluşturun.

  1. Önceden tanımlanmış RP kimliğini kullanma: Önceden tanımlanmış uygun RP kimliğini sabit bir dize olarak kullanın.
  2. Sorgulama oluşturma: Yüksek entropili, kriptografik olarak güvenli rastgele bir arabellek oluşturun, bunu kullanıcının oturumunda güvenli bir şekilde saklayın ve Base64URL olarak kodlayın.
  3. Keşfedilebilir kimlik bilgileri eşleme: allowCredentials için boş bir dizi [] belirtin. Bu, keşfedilebilir kimlik bilgileri ister. Yani kullanıcının önce kullanıcı adını girmesi gerekmez. Geçiş anahtarı sağlayıcı, mevcut hesapları gösterir.
  4. Kullanıcı Doğrulama düzeyi: userVerification: "preferred" (veya kurumsal uygunluk politikaları tarafından açıkça zorunlu kılınmışsa "required") olarak ayarlayın.
    • İstenen userVerification kısıtlama düzeyi, istemciden sorgu dizeleri aracılığıyla geri iletilmek yerine seçenekler uç noktasındaki sunucu oturumu kaydında kalıcı hale getirilmelidir. Bu sayede doğrulama uç noktası, istemci manipülasyonu riski olmadan katı eşleşme kısıtlamalarını güvenli bir şekilde uygulayabilir.
// Options generation example (discoverable flow)
const options = {
  challenge: serverGeneratedBase64UrlChallenge, // High-entropy random challenge stored in session
  rpId: "example.com",
  allowCredentials: [], // Request discoverable passkeys
  userVerification: "preferred",
};

// Persist expected UV level to user session
req.session.expectedUserVerification = "preferred";

Doğrulama uç noktası

Kullanıcının kimliğini doğrulamak için istemci tarafından döndürülen onayı güvenli bir şekilde doğrulayın:

  1. Oturum sorgulamasını doğrula: İstemci yanıtı ile oturumda depolanan beklenen sorgulama arasında katı sorgulama eşleşmesini zorunlu kılın.
  2. Kullanıcı görünümü tercihlerini zorunlu kılma:
    • Oturumun expectedUserVerification değeri "preferred" ise requireUserVerification: false değerini sunucu tarafı doğrulama kitaplığınıza ileterek UV içermeyen kimlik doğrulayıcılara (ör. kimlik doğrulayıcı ekran kilitleri devre dışı bırakılmışsa) izin verin. İstenirse "required" biyometrik/PIN girişini zorunlu kılın.
  3. Temiz Sunucu Hatası 404: İstemci tarafından döndürülen kimlik bilgisi kimliği veritabanında bulunamazsa istemcinin Signal API'yi tetikleyebilmesi için açık bir HTTP 404 hatası döndürün.

İstemci Tarafı Mantığı

HTML Formu Ek Açıklaması

Koşullu kullanıcı arayüzünden yerel olarak yararlanmak için kullanıcı adı ve şifre girişlerinize ek açıklama ekleyin. Otomatik tamamlama jetonları, webauthn spesifikasyon parametrelerini birleştirir ve otomatik odaklama, girişe odaklanıldığında tarayıcının otomatik doldurma pop-up'ını hemen tetikler.

<!-- Autocomplete tokens must contain webauthn space-separated -->
<form id="signin-form">
  <input
    type="text"
    name="username"
    autocomplete="username webauthn"
    autofocus
    data-testid="username-field"
  />
  <input type="password" name="password" autocomplete="current-password" />
  <button type="submit">Sign in</button>
</form>

Açık Düğme Akışı

Kullanıcı "Geçiş anahtarıyla oturum aç" düğmesini tıkladığında geçiş anahtarı kimlik doğrulamasını tetikleyin. Geçiş anahtarı istemini çağırmadan önce devam eden form otomatik doldurma (koşullu alma) çağrılarını iptal edin.

Koşullu Uyumlulaştırma Akışı (Form Otomatik Doldurma)

Kullanıcılar oturum açma alanlarına odaklandığında geçiş anahtarı kimlik doğrulamasını yerel olarak sunmak için sayfa yüklenirken form otomatik doldurma önerilerini etkinleştirin:

  1. Özellik algılama: Sayfa yüklendiğinde PublicKeyCredential.getClientCapabilities() yöntemini çağırın ve conditionalGet kullanılamıyorsa geçiş anahtarıyla oturum açma adımını atlayın.
  2. Kod çözme seçenekleri: Alınan kimlik bilgisi JSON nesnesinin kodunu PublicKeyCredential.parseRequestOptionsFromJSON() ile çözün.
  3. Koşullu Get'i çağırın: mediation: "conditional" ile navigator.credentials.get() yöntemini çağırın ve bir AbortController sinyali iletin. Bu işlem, geçiş anahtarı iletişim kutusu oluşturmadan otomatik doldurma özelliğini sessizce kaydeder.
  4. Try/Catch İstisna Ayrımı: navigator.credentials.get çağrısını try/catch bloğuna sarmalayın:
    • NotAllowedError: Kullanıcı, geçiş anahtarı giriş istemini iptal etti veya istemin süresi doldu.
    • AbortError: Kimlik doğrulama isteği programatik olarak iptal edildi.
  5. Call Signal API: Sunucu doğrulama fetch() çağrısını bir try/catch bloğuna sarmalayın:
    • Kullanıcının sorunun ne olduğunu anlaması için bir hata mesajı gösterin.
    • signalUnknownCredential() yöntemini YALNIZCA sunucu açıkça HTTP durumu 404 (Kimlik bilgisi bulunamadı) ile yanıt verdiğinde ve kullanıcının kimliği doğrulanmadığında çağırın.
    • signalUnknownCredential() yöntemine iletilen credentialId parametresi, ham ArrayBuffer nesnesi credential.rawId DEĞİL, Base64URL ile kodlanmış kimlik bilgisi kimliği dizesi (ör. encoded.id) OLMALIDIR.
  6. Yanıtı kodlama: Doğrulama için sunucuya göndermeden önce AuthenticatorAssertionResponse kimlik bilgisini .toJSON() ile kodlayın.
// optionsFetch and loginVerifyFetch are app-defined HTTP methods
import { optionsFetch, loginVerifyFetch } from "./api.js";

let autofillAbortController = new AbortController();

async function initializeConditionalAutofill() {
  // Feature detect Conditional Get autofill support
  const capabilities = await PublicKeyCredential.getClientCapabilities();
  if (capabilities.conditionalGet === true) {
    const loginOptionsJSON = await optionsFetch();
    const publicKey =
      PublicKeyCredential.parseRequestOptionsFromJSON(loginOptionsJSON);

    try {
      // Initiate Conditional UI form autofill suggestions
      const credential = await navigator.credentials.get({
        publicKey,
        signal: autofillAbortController.signal,
        mediation: "conditional",
      });

      // Segregated verification fetch
      const encoded = credential.toJSON();
      const response = await loginVerifyFetch(encoded);
      if (!response.ok && response.status === 404) {
        // Note: this code path runs pre-authentication, satisfying the unauth precondition
        if (PublicKeyCredential.signalUnknownCredential) {
          await PublicKeyCredential.signalUnknownCredential({
            rpId, // RP ID must match the one defined on the server
            credentialId: encoded.id,
          });
        }
      }
    } catch (err) {
      // Silently swallow expected client WebAuthn exceptions
      if (["NotAllowedError", "AbortError"].includes(err.name)) {
        return;
      }
      console.error("Unexpected conditional get error:", err);
    }
  }
}

async function triggerButtonAuthentication() {
  // Abort any pending Conditional Get call to prevent passkey prompt collisions
  autofillAbortController.abort();
  autofillAbortController = new AbortController(); // Reset controller for next triggers

  const loginOptionsJSON = await optionsFetch();
  const publicKey =
    PublicKeyCredential.parseRequestOptionsFromJSON(loginOptionsJSON);

  let credential;
  try {
    // Passkey explicit prompt trigger
    credential = await navigator.credentials.get({
      publicKey,
      signal: autofillAbortController.signal,
    });
  } catch (err) {
    if (err.name === "NotAllowedError") {
      console.log("User cancelled passkey login.");
    } else if (err.name === "AbortError") {
      console.log("The authentication operation was aborted.");
    }
    // Re-arm Conditional autofill Suggestions after cancelled explicit button prompts
    initializeConditionalAutofill();
    return; // Safe exit
  }

  // Segregated verification try/catch (HTTP 404 trigger)
  const encoded = credential.toJSON();
  try {
    const response = await loginVerifyFetch(encoded);
    if (!response.ok && response.status === 404) {
      // Note: this code path runs pre-authentication, satisfying the unauth precondition
      await PublicKeyCredential.signalUnknownCredential({
        rpId, // RP ID must match the one defined on the server
        credentialId: encoded.id, // Base64URL-encoded credential ID
      });
    }
  } catch (serverErr) {
    console.error("Verification request error:", serverErr);
  }
}

// Trigger Conditional Get on load
window.addEventListener("DOMContentLoaded", initializeConditionalAutofill);

Yedek Stratejiler

Geçiş anahtarı özelliği algılama yedeği

"webauthn-polyfills"i her zaman yükleyin ve bağlama aktarın. PublicKeyCredential desteklendiği sürece PublicKeyCredential.getClientCapabilities de desteklenir.

import 'webauthn-polyfills';

Signal API Senkronizasyonu Yedeklemesi

WebAuthn Signal API (webauthn-signals), şifre yöneticilerini sunucu kimlik bilgisi durumuyla senkronize tutmak için kullanılan aşamalı bir optimizasyondur.

  • Yedek Deneyim: if (PublicKeyCredential.signalUnknownCredential) üzerinden erişim kısıtlaması uygulanır. Desteklenmiyorsa arka plan doğrulama senkronizasyonu, tarayıcı istisnaları oluşturmadan sorunsuz bir şekilde atlanır.

Kolay JSON Serileştirme Yedeklemesi

"webauthn-polyfills"i her zaman yükleyin ve bağlama aktarın. PublicKeyCredential desteklendiği sürece PublicKeyCredential.parseRequestOptionsFromJSON ve PublicKeyCredential.prototype.toJSON de desteklenir.

import 'webauthn-polyfills';