Passkey (geçiş anahtarı), kullanıcının sitede hiçbir parola ya da gizli kod girmeden oturum açmasını sağlayan, belirli bir sitedeki belirli bir hesaba bağlı bir açık/özel anahtar çiftidir. Özel anahtar kullanıcının cihazındaki ya da cihaza bağlı bir doğrulayıcıda (authenticator) kalır, açık anahtar sitenin sunucusunda saklanır. Web'de passkey'ler Web Authentication API (WebAuthn) ile kullanılır: kayıtta navigator.credentials.create(), girişte navigator.credentials.get() çağrılır; sunucu gönderdiği rastgele challenge değerinin doğrulayıcının özel anahtarıyla imzalandığını açık anahtarla doğrular.
Bu yazı MDN'in Passkeys kılavuzunu ve Web Authentication API sayfalarını temel alıyor: kayıt ve giriş akışları, create() ile get() seçenekleri, keşfedilebilir kimlik bilgileri, kapsam ve köken doğrulaması, kaybolan passkey'ler ve paroladan geçiş. Tarayıcıya özgü kodlar MDN örneklerinden uyarlandı, çalıştırılmadı; sunucu doğrulamasının mantığını gösteren son örnek ise tarayıcıda da bulunan Web Crypto API ile Node.js'te çalıştırıldı. Serinin tamamı için Web API öğrenme rehberi sayfasına bakın.
Passkey neden parolalardan daha güvenli?

Passkey ortak bir sırra değil, açık anahtarlı kriptografiye dayanır. Kullanıcı oturum açarken doğrulayıcı, sunucudan gelen challenge değerini istekte bulunan köken gibi bağlam bilgileriyle birlikte özel anahtarla imzalar; ortaya çıkan nesneye assertion denir. Sunucu sakladığı açık anahtarla imzayı doğrular ve kullanıcının oturumunu açar. MDN bu tasarımın parolanın en ciddi zayıflıklarını nasıl giderdiğini dört maddeyle özetler:
- Kullanıcı passkey değerini kendisi uydurmaz ve hatırlamaz; değeri doğrulayıcı üretir. Bu yüzden zayıf değer seçilemez ve tahmin saldırılarına açık değildir.
- Passkey'ler siteler arasında tekrar kullanılmaz; ele geçirilen bir passkey yalnızca onu oluşturan sitede işe yarar, bu da başka yerden sızan bilgilerle deneme (credential stuffing) saldırılarını boşa çıkarır.
- Sunucu hiçbir sır saklamaz, yalnızca açık anahtarı tutar. Veritabanına sızan saldırgan özel anahtarı ele geçiremez; ancak veritabanına sahte kimlik bilgisi yazabiliyorsa hesapları ele geçirebilir.
- Tarayıcı yalnızca kapsamı istekte bulunan siteyle eşleşen passkey'leri arar ve sunucu kökeni doğrulayabilir;
https://examp1e.comgibi bir oltalama sitesihttps://example.comiçin oluşturulmuş passkey'i kullanamaz.
MDN tehditlerin tamamen ortadan kalkmadığını da vurgular: yaygın kullanım görece yeni olduğu için saldırıların kullanıcının cihazına (örneğin kötü amaçlı bir doğrulayıcı kurdurmaya) ya da hesap kurtarma gibi passkey ile korunmayan parçalara yönelmesi olasıdır.
Temel kavramlar
| Kavram | Anlamı |
|---|---|
| Relying Party (RP) | Passkey ile kullanıcı doğrulayan web sitesi |
| Doğrulayıcı (authenticator) | Özel anahtarı saklayan modül: işletim sistemine gömülü, ayrı donanım anahtarı ya da kimlik yöneticisi uygulaması |
| Challenge | Sunucunun her istek için ürettiği, tahmin edilemez rastgele değer |
| Assertion | Girişte doğrulayıcının challenge ve bağlam verisini imzalamasıyla oluşan nesne |
| Attestation | Doğrulayıcının kendisi hakkında doğrulanabilir kanıt sunduğu isteğe bağlı mekanizma |
| RP ID | Passkey'in hangi alan adında kullanılabileceğini belirleyen kimlik |
WebAuthn, parolalar ve federe kimlik gibi farklı yöntemleri yöneten Credential Management API'nin bir uzantısıdır. MDN'in güvenli bağlam listesinde hem Credential Management API hem Web Authentication API yer alır; yani bu API'ler yalnızca HTTPS gibi güvenli bağlamlarda kullanılabilir. Güvenli bağlam ve ilgili hatalar için DOMException ve güvenli bağlam hataları yazısına, HTTPS kurulumu için HTTPS ve TLS rehberi yazısına bakın.
WebAuthn'in önemli özellikleri

Platform ve taşınabilir doğrulayıcılar
Platform doğrulayıcıları cihazdan çıkarılamaz; işletim sistemine gömülü Touch ID veya Windows Hello gibi. Taşınabilir (roaming) doğrulayıcılar ise başka bir cihaza takılabilir; klasik örnek USB güvenlik anahtarıdır. Platform doğrulayıcısı kullanıcı için daha rahattır, ama yalnızca kendi cihazında kullanılabilir. MDN, bir mobil cihazdaki platform doğrulayıcısının Bluetooth üzerinden dizüstü bilgisayar için taşınabilir doğrulayıcı gibi çalışabildiğini ve platform doğrulayıcılarının bulut eşitleme veya içe/dışa aktarma ile passkey paylaşabildiğini de belirtir. RP tercihini authenticatorSelection.authenticatorAttachment ile bildirir.
Keşfedilebilir ve keşfedilemez kimlik bilgileri
Keşfedilebilir kimlik bilgileri (resident key), RP kullanıcıyı önceden tanımlamadan kullanılabilir; imzalama anahtarı doğrulayıcıda saklanır ve allowCredentials boş olabilir. Keşfedilemez olanlarda ise RP önce kullanıcıyı (örneğin kullanıcı adıyla) tanımlayıp kimlik değerini get()'e geçirir; doğrulayıcı anahtarı her seferinde iç bir tohumdan ve kimlik değerinden yeniden türetir ya da şifrelenmiş haliyle çözer. Keşfedilemez kimliklerin avantajı sınırlı depolamalı bir doğrulayıcının neredeyse sınırsız hesap destekleyebilmesidir. MDN'in vurgusu açıktır: passkey'ler her zaman keşfedilebilir kimlik bilgisi olmalıdır; bunun için create() çağrısında residentKey: 'required' ve requireResidentKey: true verilir.
Kullanıcı doğrulaması ve attestation
Doğrulayıcı create() ve get() işlemleri için her zaman kullanıcının onayını ister. RP ayrıca kullanıcı doğrulaması isteyebilir: PIN veya biyometri gibi. Bu durumda işlem çok faktörlü kimlik doğrulama sayılır; doğrulayıcı "sahip olunan", PIN "bilinen", biyometri ise "olunan" etkendir. Her doğrulayıcı kullanıcı doğrulamasını desteklemez. Attestation ise doğrulayıcının üretim sırasında gömülü bir anahtarla kendisi hakkında kanıt sunmasıdır. İsteğe bağlıdır; desteklenmediğinde yanıt imzasız ya da passkey'in kendisiyle imzalı (self attestation) olabilir. MDN, RP'lerin doğrulayıcılar için izin listeleri tutmaya çalışmaması gerektiği konusunda uyarır.
Kapsam ve köken doğrulaması
Varsayılan olarak create() çağrıldığında tarayıcı passkey'in RP ID'sini çağıranın kökenindeki alan adına ayarlar; get() sırasında doğrulayıcı passkey'i yalnızca bu değer eşleşirse kullanır. Yani passkey, onu oluşturan sayfayla aynı kökenden (port hariç) kullanılabilir. Siteler bu kuralı, çağıranın alan adının kayıt edilebilir bir alan adı soneki olmak şartıyla gevşetebilir: https://register.example.com sayfası RP ID'si example.com olan bir passkey oluşturursa https://login.example.com o passkey'i kullanabilir. Buna ek olarak imzalı assertion, get()'i çağıran belgenin kökenini, çağıran bir iframe ise üst belgeyle aynı kökende olup olmadığını ve gerekiyorsa üst belgenin kökenini içerir; sunucu bu değerlerin beklediği gibi olduğunu kontrol etmelidir.
Sunucu doğrulamasının mantığı
Yanıttaki clientDataJSON, bir ArrayBuffer içinde JSON metni taşır. Çözüldüğünde type (webauthn.create veya webauthn.get), base64url kodlu challenge, origin, crossOrigin ve gerekiyorsa topOrigin alanları bulunur. authenticatorData en az 37 bayttır: ilk 32 bayt RP ID'nin SHA-256 özeti (rpIdHash), ardından bir bayt bayrak alanı ve 4 baytlık imza sayacı gelir. Bayraklarda bit 0 kullanıcı varlığı (UP), bit 2 kullanıcı doğrulaması (UV), bit 3 yedeklemeye uygunluk (BE), bit 4 yedeklenmiş olma (BS) anlamına gelir. MDN'e göre signature, authenticatorData ile clientDataJSON'ın SHA-256 özetinin birleşimi üzerinden atılmış imzadır.
Aşağıdaki örnek bu kontrolleri bir simülasyonla gösterir: doğrulayıcının yerine Web Crypto ile bir P-256 ECDSA anahtar çifti üretilir, sahte bir assertion imzalanır ve sunucu tarafı fonksiyon türü, challenge'ı, kökeni, rpIdHash değerini ve imzayı kontrol eder. Kod ağ isteği yapmaz; tarayıcıda (güvenli bağlamda) ve Node.js'te aynı çalışır.
const b64url = (bayt) =>
btoa(String.fromCharCode(...new Uint8Array(bayt))).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const sha256 = (veri) => crypto.subtle.digest('SHA-256', veri);
const metin = (s) => new TextEncoder().encode(s);
console.log(b64url(new Uint8Array([0x8c, 0x0a, 0x26, 0xff]))); // jAom_w
// --- Sunucu: kayıtta saklanan beklentiler
const RP_ID = 'example.com';
const BEKLENEN_KOKEN = 'https://example.com';
const challenge = crypto.getRandomValues(new Uint8Array(32));
// --- Doğrulayıcı ve tarayıcı yerine geçen simülasyon
const anahtar = await crypto.subtle.generateKey({ name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign', 'verify']);
async function sahteAssertion(koken) {
const clientDataJSON = metin(JSON.stringify({ type: 'webauthn.get', challenge: b64url(challenge), origin: koken, crossOrigin: false }));
const authenticatorData = new Uint8Array(37);
authenticatorData.set(new Uint8Array(await sha256(metin(RP_ID))), 0);
authenticatorData[32] = 0b00011101; // UP, UV, BE, BS
const imzalanan = new Uint8Array([...authenticatorData, ...new Uint8Array(await sha256(clientDataJSON))]);
const signature = await crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, anahtar.privateKey, imzalanan);
return { clientDataJSON, authenticatorData, signature };
}
// --- Sunucu tarafı kontroller
async function dogrula({ clientDataJSON, authenticatorData, signature }) {
const cd = JSON.parse(new TextDecoder().decode(clientDataJSON));
if (cd.type !== 'webauthn.get') return 'red: tür';
if (cd.challenge !== b64url(challenge)) return 'red: challenge';
if (cd.origin !== BEKLENEN_KOKEN) return 'red: köken ' + cd.origin;
const rpHash = new Uint8Array(await sha256(metin(RP_ID)));
if (!rpHash.every((b, i) => b === authenticatorData[i])) return 'red: rpIdHash';
const bayrak = authenticatorData[32];
const imzalanan = new Uint8Array([...authenticatorData, ...new Uint8Array(await sha256(clientDataJSON))]);
const imzaGecerli = await crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, anahtar.publicKey, signature, imzalanan);
if (!imzaGecerli) return 'red: imza';
return JSON.stringify({ UP: !!(bayrak & 1), UV: !!(bayrak & 4), BE: !!(bayrak & 8), BS: !!(bayrak & 16) });
}
console.log(await dogrula(await sahteAssertion('https://example.com')));
// {"UP":true,"UV":true,"BE":true,"BS":true}
console.log(await dogrula(await sahteAssertion('https://examp1e.com')));
// red: köken https://examp1e.com
const bozuk = await sahteAssertion('https://example.com');
bozuk.authenticatorData[32] = 0b00000001;
console.log(await dogrula(bozuk)); // red: imzaİlk çağrı tüm kontrollerden geçti ve bayrakları çözdü. İkinci çağrıda oltalama alan adından gelen köken reddedildi; gerçek dünyada tarayıcı bu sitede passkey'i zaten sunmazdı, sunucudaki köken kontrolü ikinci bir katmandır. Üçüncü çağrıda imzadan sonra bayrak baytı değiştirildi ve imza doğrulaması başarısız oldu, çünkü authenticatorData imzanın kapsamındadır. Bu simülasyon yalnızca mantığı öğretmek içindir: gerçek yanıtlarda açık anahtar COSE biçiminde gelir (MDN, DER biçimini getPublicKey() ile almanın mümkün olduğunu belirtir), attestation nesnesinin ve imzanın ayrıştırılması ek adımlar gerektirir. Üretimde olgun bir WebAuthn sunucu kütüphanesi kullanın. Web Crypto metotlarının ayrıntısı Web Crypto API yazısında.
Kaybolan passkey'ler ve yönetim
Kullanıcı bir doğrulayıcıyı kaybederse içindeki tüm passkey'leri de kaybeder. MDN iki strateji önerir. Birincisi, parolaların aksine tek hesap için birden fazla passkey oluşturmaktır: günlük kullanım için platform doğrulayıcısında bir passkey, cihaz kaybına karşı güvenli bir yerde saklanan taşınabilir doğrulayıcıda bir yedek. excludeCredentials listesi yeni passkey'in farklı bir doğrulayıcıda oluşmasını sağlar. İkincisi yedeklemedir: get() yanıtındaki BE ve BS bayrakları passkey'in yedeklenebilir olup olmadığını ve yedeklenip yedeklenmediğini söyler. Yedeklenemeyen bir passkey için RP kullanıcıyı ikinci bir passkey oluşturmaya davet edebilir.
RP, oturum açmış kullanıcının hesabına kayıtlı passkey'leri görmesine, adlandırmasına ve silmesine izin veren bir arayüz sunmalıdır. MDN her anahtar için sağlayıcı adını (AAGUID üzerinden bulunur), son kullanım zamanını ve yedekleme durumunu göstermeyi önerir. Son passkey silinmek istendiğinde sonuçları açıklanmalıdır. Sunucuda silinen bir passkey doğrulayıcıda kalmaya devam ettiği için tarayıcı onu yine önerebilir; bu uyumsuzluğu azaltmak için PublicKeyCredential.signalUnknownCredential() (tanınmayan kimliği bildirir, kimliği doğrulanmamış kullanıcı için de güvenle çağrılabilir), signalAllAcceptedCredentials() ve signalCurrentUserDetails() statik metotları vardır; son ikisi kullanıcı verisi açığa çıkardığı için yalnızca kimliği doğrulanmış kullanıcılar için çağrılmalıdır. Tarayıcı desteği için MDN uyumluluk tablosuna bakın.
Paroladan passkey'e geçiş
MDN üç adımlı bir geçiş önerir. Önce kullanıcılar parolayla başarılı giriş yaptıktan sonra passkey oluşturmaya davet edilir. Koşullu oluşturma (conditional create) bunu kesintisiz hale getirir: create() çağrısına mediation: 'conditional' verilir; kullanıcı passkey destekleyen bir kimlik yöneticisindeki parolasıyla az önce giriş yaptıysa tarayıcı ona sormadan passkey oluşturur, aksi halde çağrı hata ile reddedilir ve site bu hatayı sessizce yok sayabilir. MDN, koşullu oluşturmadan önce bekleyen tüm WebAuthn çağrılarının iptal edilmesi gerektiğini, çapraz kökenli iframe'den çağrılamayacağını ve bu durumda dönen verideki kullanıcı varlığı (UP) bayrağının her zaman false olacağını, dolayısıyla sunucunun bu bayrağı zorunlu tutmaması gerektiğini not eder.
İkinci adım otomatik doldurma arayüzüyle passkey'i parolanın yanında kullanılabilir kılmaktır. Üçüncü adım parolayı emekliye ayırmaktır: kullanıcı passkey'i tercih etse bile hesabında parola durduğu sürece tahmin, credential stuffing ve oltalama saldırılarına açık kalır. Ancak parola, passkey erişimi kaybolduğunda kilitlenmeyi de önler; parolayı silmeye teşvik etmeden önce kullanıcının farklı doğrulayıcılarda birden fazla passkey'i ya da yedeklenmiş passkey'leri olduğundan emin olun.
iframe içinde ve izin politikaları
WebAuthn erişimi Permissions-Policy başlığının publickey-credentials-create ve publickey-credentials-get yönergeleriyle denetlenir; ikisinin varsayılan izin listesi self'tir. get() üst belgeyle aynı kökenden yüklenen iç içe bağlamlarda da kullanılabilir. Çapraz kökenli bir iframe'de kullanmak için hem gömen sayfanın iframe'e allow özniteliğiyle izin vermesi hem de RP sitesinin Permissions-Policy başlığıyla izin vermesi gerekir; çapraz kökenli create() için çerçevenin geçici kullanıcı etkinliğine de sahip olması şarttır. Politika engellediğinde dönen promise NotAllowedError ile reddedilir. Geçici kullanıcı etkinliği kavramı Web API hataları yazısında anlatılıyor.
Permissions-Policy: publickey-credentials-get=("https://subdomain.example.com")
Permissions-Policy: publickey-credentials-create=("https://subdomain.example.com")Sık yapılan hatalar

- Challenge'ı istemcide üretmek veya yeniden kullanmak. Yeniden oynatma saldırılarına kapı açar.
- Keşfedilemez kimlik oluşturmak. Passkey'ler keşfedilebilir olmalıdır;
residentKey: 'required'verin. - Sunucuda kökeni ve RP ID'yi kontrol etmemek. Oltalamaya karşı ikinci savunma katmanını kaybedersiniz.
- Tek passkey ile yetinmek. Cihaz kaybında kullanıcı hesabına erişemeyebilir; yedek passkey ve kurtarma yolu sunun.
- Hesap kurtarmayı zayıf bırakmak. MDN, saldırıların passkey ile korunmayan bu tür parçalara yönelebileceğini belirtir.
- HTTP üzerinde denemek. WebAuthn yalnızca güvenli bağlamda çalışır.
- Bekleyen koşullu get çağrısını iptal etmeden yeni çağrı yapmak.
AbortControllerile önceki işlemi sonlandırın.
Sık Sorulan Sorular
Passkey ile WebAuthn aynı şey mi?
Web'de passkey'ler Web Authentication API ile uygulanır. WebAuthn daha geniş bir API'dir ve keşfedilemez kimlikler gibi passkey tanımına girmeyen kullanımları da kapsar; passkey ise tanım gereği keşfedilebilir bir WebAuthn kimlik bilgisidir.
Sunucu veritabanım sızarsa ne olur?
Sunucuda yalnızca açık anahtarlar bulunduğu için saldırgan bunlarla oturum açamaz. Ancak MDN, saldırgan veritabanına sahte kimlik bilgisi yazabilirse hesapları ele geçirebileceği uyarısını yapar; yazma erişimini korumaya devam edin.
Kullanıcı farklı bir cihazda nasıl giriş yapar?
Passkey bulut eşitleme ile diğer cihazlara taşınmış olabilir ya da mobil cihazdaki platform doğrulayıcısı yakındaki bir bilgisayar için taşınabilir doğrulayıcı gibi çalışabilir. Ayrıca kullanıcının farklı doğrulayıcılarda birden fazla passkey kaydetmesine izin verin.
Parola alanını hemen kaldırmalı mıyım?
MDN önce passkey'i parolanın yanında sunmayı, otomatik doldurma arayüzüyle kullanımını kolaylaştırmayı ve parolayı ancak kullanıcının yeterli yedeği olduğunda emekliye ayırmayı önerir.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Web API öğrenme rehberi · Web Crypto API · DOMException ve güvenli bağlam · CSRF saldırısı · Set-Cookie öznitelikleri · HTTPS ve TLS rehberi · JSON.parse ve stringify