Passkeys, parolaların yerine kullanıcının kimlik doğrulayıcısı tarafından oluşturulan bir anahtar çifti koyar: Windows Hello, iCloud Keychain, Google Password Manager, bir parola yöneticisi veya bir FIDO2 güvenlik anahtarı. Özel anahtar asla kimlik doğrulayıcıdan çıkmaz, sunucu yalnızca genel anahtarı saklar ve imza sitenin alan adına bağlıdır, bu nedenle passkey'ler kimlik avına uğratılamaz, yeniden kullanılamaz veya sunucu veritabanından sızdırılamaz.
Teknik olarak bir passkey, discoverable bir WebAuthn kimlik bilgisidir (resident key): kimlik doğrulayıcı, kullanıcı tanıtıcısını özel anahtarın yanında tutar, böylece kullanıcı bir kullanıcı adı yazmadan oturum açabilir ve tarayıcı, sitenin passkey'lerini kullanıcı adı alanının otomatik doldurma listesinde sunabilir. TsgcWSAPIServer_WebAuthn sunucusu, aşağıda açıklanan ayarlar ve olaylarla passkey'leri destekler.
WebAuthn sunucusu, sgcWebSockets Enterprise ve sgcAuth paketine dahildir. WebAuthn güvenli bir bağlam (secure context) gerektirir: sayfaları https üzerinden (veya localhost) sunun ve WebAuthnOptions.RelyingParty değerini tarayıcıda gösterilen ana bilgisayar adına ayarlayın.
WebAuthnOptions.DefaultOptions.Registration.DiscoverableCredential özelliği, kayıt seçeneklerinde istenen residentKey değerini ayarlar:
| Değer | residentKey | Meaning |
|---|---|---|
waundcPreferred (default) | preferred | Kimlik doğrulayıcı, mümkün olduğunda discoverable bir kimlik bilgisi oluşturur. |
waundcRequired | required | Yalnızca discoverable kimlik bilgileri kabul edilir. Bunu passkey'ler ve kullanıcı adsız oturum açma için kullanın. |
waundcDiscouraged | discouraged | Kimlik doğrulayıcı, oturum açmak için kullanıcı adına ihtiyaç duyan bir server-side kimlik bilgisi oluşturmalıdır. |
Bir kayıt isteği, discoverable_credential alanıyla ("required", "preferred" veya "discouraged") varsayılanı geçersiz kılabilir. Ayar yalnızca istek kendisi belirli bir residentKey talep etmediğinde uygulanır.
WebAuthn.WebAuthnOptions.RelyingParty := 'www.example.com';
// ... passkeys are discoverable credentials: require a resident key
WebAuthn.WebAuthnOptions.DefaultOptions.Registration.DiscoverableCredential :=
waundcRequired;
WebAuthn.WebAuthnOptions.DefaultOptions.Registration.UserVerification :=
waunuvPreferred;
WebAuthn.WebAuthnOptions.DefaultOptions.Authentication.UserVerification :=
waunuvPreferred;
Bir kullanıcının genellikle birden fazla passkey'i vardır, örneğin biri bilgisayarda biri telefonda. Her birini aynı kullanıcı adıyla kaydedin: kullanıcı adının sunucu tarafından bilinen kimlik bilgileri zaten varsa, kayıt işlemi bunların kullanıcı tanıtıcısını (user.id) yeniden kullanır, böylece hesabın her passkey'i tek bir kullanıcı tanıtıcısını paylaşır. Sunucu, çalışırken kaydedilen kimlik bilgilerini ve AddCredential ile eklenenleri bilir, bu nedenle kimlik bilgileriniz bir veritabanında yaşıyorsa, hesap başına tek bir kullanıcı tanıtıcısı korumak için bunları başlangıçta ekleyin. Bir kullanıcı adıyla, kimlik doğrulama seçenekleri kullanıcının her passkey'ini allowCredentials içinde içerir.
Kimlik doğrulama seçeneklerini boş bir kullanıcı adıyla isteyin. Seçenekler o zaman hiçbir allowCredentials taşımaz, tarayıcı relying party için sahip olduğu passkey'leri gösterir ve kullanıcı birini seçer. Sunucu, yanıtın userHandle değerini gerektirir ve bunun kullanılan kimlik bilgisine ait olduğunu kontrol eder, uygulama tarafından bilinmeyen bir kimlik bilgisi ise reddedilir.
// ... "Sign in with a passkey" button: the browser lists the passkeys of the site
const options = await post("/sgcWebAuthn/Authentication/Options", {username: ""});
const assertion = await SimpleWebAuthnBrowser.startAuthentication(options);
const result = await post("/sgcWebAuthn/Authentication/Verify", assertion);
Koşullu aracılıkla (conditional mediation), passkey'ler kullanıcı adı alanının otomatik doldurma listesinde, kaydedilmiş parolaların yanında görünür. Girdiye autocomplete="username webauthn" ekleyin, /sgcWebAuthn.js yükleyin (bileşen tarafından sunulur, uç nokta EndpointsOptions.WebAuthn) ve sayfa yüklendiğinde kullanıcı adsız seçeneklerle startAuthentication(options, true) çağırın. Kullanıcı bir passkey seçtiğinde promise çözülür.
<script src="/sgcWebAuthn.js"></script>
<input id="username" name="username" autocomplete="username webauthn">
<script>
async function post(url, body) {
const r = await fetch(url, {method: "POST",
headers: {"Content-Type": "application/json"}, body: JSON.stringify(body)});
return r.json();
}
async function passkeyAutofill() {
if (!(await SimpleWebAuthnBrowser.browserSupportsWebAuthnAutofill())) return;
// ... no user name: the options carry no allowCredentials
const options = await post("/sgcWebAuthn/Authentication/Options", {username: ""});
// ... true: conditional mediation, the passkeys appear in the autofill list
const assertion = await SimpleWebAuthnBrowser.startAuthentication(options, true);
const result = await post("/sgcWebAuthn/Authentication/Verify", assertion);
if (result.verified) { location.href = "/"; }
}
passkeyAutofill();
</script>
Otomatik doldurma (autofill), koşullu aracılığı (conditional mediation) destekleyen bir tarayıcı gerektirir (güncel Chrome, Edge ve Safari). browserSupportsWebAuthnAutofill(), diğer tarayıcılarda False döndürür, bunlar için bir "Passkey ile oturum aç" düğmesi bulundurun.
Bileşen kimlik bilgilerini kalıcı hale getirmez. Dört olay, bunu deponuza bağlar:
| Olay | Ne yapmalı |
|---|---|
| OnWebAuthnRegistrationSuccessful | aCredentialRecord değerini, örneğin aCredentialRecord.AsJSON, CredentialId ve Username değerleriyle birlikte kaydedin. |
| OnWebAuthnAuthenticationOptionsRequest | Bir kullanıcı adıyla, o kullanıcının passkey'lerini CredentialRecords içine ekleyin. Kullanıcı adı olmadan, hiçbir şey eklemeyin. |
| OnWebAuthnAuthenticationGetCredential | Tarayıcının kullandığı kimlik bilgisi ceremony'nin kimlik bilgisi listesinde yoksa tetiklenir; bu, kullanıcı adsız veya otomatik doldurmalı bir oturum açmada geçerlidir. Kimlik bilgisi kimliğini arayın, aCredentialRecord değerini doldurun ve Found değerini True olarak ayarlayın. |
| OnWebAuthnAuthenticationSuccessful | SignCount ve BackupState değerlerini aAuthentication.Credential.CredentialRecord için güncelleyip kaydedin ve oturumu oluşturun. |
// ... 1. registration: store the new passkey (the whole record as JSON)
procedure TForm1.WebAuthnWebAuthnRegistrationSuccessful(Sender: TObject;
const aRegistration: TsgcWebAuthn_Registration;
const aCredentialRecord: TsgcWebAuthn_CredentialRecord; var Accept: Boolean);
begin
DBInsertPasskey(aCredentialRecord.CredentialId, aCredentialRecord.Username,
aCredentialRecord.AsJSON);
end;
// ... 2. sign in with a user name: the passkeys of that user
procedure TForm1.WebAuthnWebAuthnAuthenticationOptionsRequest(Sender: TObject;
const aRequest: TsgcWebAuthn_AuthenticationOptions_Request;
var CredentialRecords: TsgcWebAuthn_CredentialRecords; var Accept: Boolean);
var
i: Integer;
oList: TStringList;
begin
if aRequest.Username = '' then
Exit; // ... usernameless or autofill: no allowCredentials
oList := TStringList.Create;
try
DBGetPasskeysOfUser(aRequest.Username, oList); // ... one JSON per line
for i := 0 to oList.Count - 1 do
CredentialRecords.AddCredentialRecordFromJSON(oList[i]);
Accept := oList.Count > 0;
finally
oList.Free;
end;
end;
// ... 3. usernameless and autofill: find the passkey by its credential id
procedure TForm1.WebAuthnWebAuthnAuthenticationGetCredential(Sender: TObject;
const aCredentialId: string;
const aCredentialRecord: TsgcWebAuthn_CredentialRecord; var Found: Boolean);
var
vJSON: string;
begin
Found := DBFindPasskey(aCredentialId, vJSON);
if Found then
aCredentialRecord.ReadJSON(vJSON);
end;
// ... 4. signed in: save the new signature counter and backup state
procedure TForm1.WebAuthnWebAuthnAuthenticationSuccessful(Sender: TObject;
const aRequest: TsgcWebAuthn_AuthenticationVerify_Request;
const aAuthentication: TsgcWebAuthn_Authentication; var Accept: Boolean);
var
oRecord: TsgcWebAuthn_CredentialRecord;
begin
oRecord := aAuthentication.Credential.CredentialRecord;
DBUpdatePasskey(oRecord.CredentialId, oRecord.AsJSON);
CreateUserSession(oRecord.Username);
end;
OnWebAuthnAuthenticationGetCredential tarafından döndürülen kayıt, istenen CredentialId değerine ve ceremony bir kullanıcı adıyla başlatıldıysa aynı Username değerine sahip olmalıdır, aksi halde oturum açma başarısız olur. Saklanan kayıtta UserId (kullanıcı tanıtıcısı) değerini tutun, bu değer kimlik doğrulayıcının gönderdiği userHandle ile karşılaştırılır. Olaylar sunucu bağlantı iş parçacıklarında çalışır, paylaşılan kaynakları buna göre koruyun.
Kimlik doğrulayıcı verisi bayrakları, sunucunun ne tür bir passkey aldığını belirtir. Bunlar, kimlik bilgisi kaydında BackupEligible (BE bayrağı) ve BackupState (BS bayrağı) olarak saklanır:
| BackupEligible | BackupState | Meaning |
|---|---|---|
| True | True | Platform veya parola yöneticisi tarafından yedeklenen ve kullanıcının diğer cihazlarında kullanılabilen senkronize passkey. |
| True | False | Henüz yedeklenmemiş çok cihazlı passkey. |
| False | False | Cihaza bağlı (device-bound) passkey, örneğin bir güvenlik anahtarı veya cihazdan asla çıkmayan bir TPM anahtarı. Kullanıcıya ikinci bir passkey kaydetmesini önerin. |
function GetPasskeyKind(aRecord: TsgcWebAuthn_CredentialRecord): string;
begin
if aRecord.BackupEligible and aRecord.BackupState then
Result := 'synced passkey'
else if aRecord.BackupEligible then
Result := 'multi-device passkey, not backed up yet'
else
Result := 'device-bound passkey';
end;
Her oturum açmada sunucu, BS bayrağı ayarlanmış ancak BE bayrağı ayarlanmamış bir yanıtı ve BE bayrağı saklanan BackupEligible değerinden farklı olan bir yanıtı reddeder, çünkü bir kimlik bilgisinin uygunluğu asla değişmez. Yeni BS bayrağı BackupState içine kopyalanır, bunu OnWebAuthnAuthenticationSuccessful içinde kaydedin.
Bazı kimlik doğrulayıcılar her oturum açmada bir imza sayacını artırır. Yanıtın sayacı veya saklanan SignCount sıfır değilse, yanıtın sayacı saklanan değerden büyük olmalıdır, aksi halde kimlik doğrulayıcı klonlanmış olabileceğinden oturum açma reddedilir. Saklanan değer yalnızca ileriye gider ve algılamanın çalışması için her oturum açmadan sonra kaydedilmelidir. Senkronize passkey'ler genellikle her zaman 0 bildirir, bu da bu kontrolü onlar için devre dışı bırakır. Artmayan bir sayacı kabul etmek için WebAuthnOptions.AllowSignCountLessOrEqualStoredValue değerini True olarak ayarlayın (saklanan değer yine de asla düşürülmez).
Demos\26.Authentication\01.Passkeys demosu, kullanıcı başına birden fazla passkey kaydeder, kullanıcı adı olmadan ve otomatik doldurmayla oturum açar, her passkey'in senkronize mi yoksa cihaza bağlı mı olduğunu gösterir ve kimlik bilgilerini yukarıdaki olaylar aracılığıyla uygulamanın bir JSON dosyasında saklar. Ayrıca WebAuthn ve JavaScript istemcisine bakın.