parolasız giriş etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
parolasız giriş etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

15 Aralık 2025 Pazartesi

WebAuthn ve Passkeys ile Parolasız Giriş: Node.js Üzerinde Adım Adım Entegrasyon Rehberi

Giriş

Parolalar hem kullanıcılar hem de geliştiriciler için yıllardır en zayıf halka. Güvenlik ihlalleri, kimlik avı ve kullanım zorluğu derken şifrelerin yerini alan yeni standartlar artık olgunlaştı: WebAuthn ve Passkeys. Apple, Google ve Microsoft ekosistemlerinde yerleşik olan Passkey’ler; biyometrik doğrulama (Face ID, Touch ID), cihaz PIN’i veya güvenlik anahtarlarıyla kullanıcıyı güvenli ve hızlı şekilde doğrular. Bu yazıda, Node.js üzerinde modern bir Passkey (FIDO2/WebAuthn) entegrasyonunu adım adım ele alacağız.

Neden Passkey?

Passkey, kullanıcı tarafında kriptografik anahtar çifti üretir ve yalnızca kayıt olduğunuz alan adıyla (relying party) çalışır. Bu sayede kimlik avı saldırılarına karşı dirençlidir, veri sızıntılarında şifre yeniden kullanımı riski yoktur ve kullanıcı deneyimi önemli ölçüde iyileşir. iCloud Anahtar Zinciri ve Google Password Manager gibi yöneticiler, passkey’leri cihazlar arasında senkronize edebilir; güvenlik anahtarları (YubiKey, Titan Key) ise kurumsal ortamda donanımsal güvence sağlar.

Mimari ve Akış

WebAuthn entegrasyonu iki ana adımdan oluşur: Kayıt ve Giriş. Kayıtta sunucu bir challenge üretir, tarayıcı üzerinden kullanıcının cihazında anahtar çifti oluşturulur ve sunucuya attestation verisiyle beraber kimlik bilgisi (credential) kaydedilir. Girişte sunucu yine bir challenge üretir, cihaz özel anahtarla imzalar ve sunucu imzayı, sayacı (signCount) ve relying party değerlerini doğrular.

Ön Koşullar ve Güvenlik

Üretimde mutlaka HTTPS kullanın; WebAuthn tarayıcılar tarafından güvenli bağlam gerektirir. rpID alan adınız (ör. example.com), origin ise tam köken (ör. https://example.com) olmalıdır. Oturum yönetimi için HttpOnly, Secure ve SameSite cookie ayarlarına dikkat edin. Kullanıcı deneyimini iyileştirmek için mümkünse User Verification değerini required seçin; böylece biyometrik veya PIN doğrulaması zorunlu olur.

Sunucu Kurulumu (Node.js)

Sunucuda Express veya benzeri bir framework kullanabilirsiniz. WebAuthn doğrulama ve dönüşüm adımlarında kendiniz sıfırdan implementasyon yazmak yerine toplulukta yaygın kütüphanelerden yararlanmak geliştirme hızını artırır. Örneğin @simplewebauthn/server ve @simplewebauthn/browser ikilisi, kurulum ve doğrulama mantığını açık biçimde sunar. Kayıt için iki uç nokta tanımlayın: /register/options (kayıt seçenekleri üretir) ve /register/verify (kayıt yanıtını doğrular). Giriş için benzer şekilde /login/options ve /login/verify oluşturun.

Kayıt seçeneklerini üretirken rpID ve rpName (marka adınız), user.id (stabil benzersiz kimlik), user.name (kullanıcı adı/e‑posta), user.displayName, authenticatorSelection ve attestationType gibi alanları doldurun. Çoğu senaryo için attestationType = none, residentKey = preferred veya passkey zorunlu ise required, userVerification = required iyi varsayılanlardır. Challenge değerini kriptografik olarak güçlü bir rastgelelik ile üretin ve oturumda kısa süreli saklayın.

Kullanıcı tarayıcıda kayıt yanıtı döndüğünde sunucu tarafında doğrulama yapın. Başarılı doğrulamada şu bilgileri veritabanında tutun: credentialID (base64url), credentialPublicKey, counter (signCount), transports ve tercihen cihaz adına ilişkin meta. Bu kayıtlar kullanıcı hesabıyla ilişkilendirilir ve çoklu cihaz/passkey senaryolarında birden fazla credential desteklenir.

İstemci (Tarayıcı) Entegrasyonu

Tarayıcı tarafında navigator.credentials.create() çağrısı kayıt için, navigator.credentials.get() ise giriş için kullanılır. Modern tarayıcılarda Conditional UI desteği ile kullanıcı “Parolayı yazmadan” doğrudan giriş önerisi alabilir. Özellikle mobilde bu deneyim, geleneksel şifre formuna göre çok daha hızlıdır. İstemci kodunda sunucudan gelen PublicKeyCredentialCreationOptions veya PublicKeyCredentialRequestOptions nesnelerini doğru şekilde dönüştürmeyi (ArrayBuffer <-> base64url) unutmayın.

Passkey’lerin bulut senkronizasyonu açıksa (iCloud/Google Password Manager), kullanıcı yeni cihazında da sorunsuz giriş yapabilir. Kurumsal katmanda donanımsal güvenlik anahtarlarıyla (USB/NFC/BLE) aynı akışı takip edersiniz; sadece transports alanı farklılık gösterir.

Giriş Doğrulaması ve Sayaç Yönetimi

Girişte sunucu challenge üretir ve tarayıcı assertion döndürür. Sunucu, imzayı credentialPublicKey ile doğrular, rpIDHash ve origin kontrolünü yapar ve signCount değerini karşılaştırır. Donanım bazı senaryolarda sayacı artıramayabilir; bu durumda kütüphanenin “signCount kludge” dokümantasyonuna bakın. Başarılı doğrulamada kullanıcı oturumunu açın ve sayacı güncelleyin.

UX, Erişilebilirlik ve Geri Dönüş Planı

Kullanıcıya net talimatlar verin: “Cihazınızın biyometrisini kullanın” gibi. Geri dönüş planı olarak e‑posta tabanlı sihirli bağlantı veya tek kullanımlık kurtarma kodları tanımlayın. Güvenlik anahtarlarını tercih eden kullanıcılar için “harici anahtar ekle” seçeneği sunun. Çoklu cihaz desteğinde, kullanıcı hesabı ayarlarına eklenen/çıkarılan tüm kimlik bilgilerini listelemeniz önemlidir.

Uyumluluk ve Test

Geliştirme sürecinde webauthn.io veya tarayıcı geliştirici araçlarının “Security” ve “WebAuthn” panellerinden yararlanın. iOS Safari, Android Chrome, macOS ve Windows ortamlarında çapraz test yapın. Alan adı ve köken uyuşmazlığı en yaygın hatadır; yerelde test ederken localhost için HTTPS kullanmayı veya tünel servisleriyle geçici bir alan adı oluşturmayı düşünün.

Sonuç

Passkey ve WebAuthn, parolasız geleceğin pratik ve güvenli yolu. Node.js üzerinde birkaç uç nokta ve doğru yapılandırma ile güncel tarayıcılar ve işletim sistemleri arasında kusursuz bir deneyim sunabilirsiniz. Doğru varsayılanlar (attestation none, userVerification required), güçlü bir TLS yapılandırması, sağlam oturum yönetimi ve iyi bir geri dönüş planı ile projenizi bugün üretime taşımanız mümkün.

11 Aralık 2025 Perşembe

Next.js ile WebAuthn (Passkey) Entegrasyonu: Parolasız Giriş Adım Adım

Parolasız kimlik doğrulama, kullanıcı deneyimini iyileştirirken hesap güvenliğini artırmanın en pratik yolu haline geldi. WebAuthn ve Passkey teknolojileri sayesinde kullanıcılar artık SMS kodları veya karmaşık parolalar yerine cihazlarındaki biyometrik sensörlerle giriş yapabiliyor. Bu yazıda, Next.js üzerinde WebAuthn tabanlı Passkey entegrasyonunu adım adım anlatarak, hem teknik detayları hem de üretim ortamı için ipuçlarını paylaşacağım.

Kısaca özetlemek gerekirse, WebAuthn FIDO2 standardının bir parçasıdır ve tarayıcılar aracılığıyla donanımsal (Güvenli Anahtarlar, Touch ID, Windows Hello vb.) veya platform tabanlı kimlik doğrulayıcılarla anahtar çifti üretip doğrulamayı mümkün kılar. RP (Relying Party) ID, origin ve challenge mekanizmaları doğru kurgulandığında, hem phishing dirençli hem de kullanıcı dostu bir giriş deneyimi elde edersiniz.

Önkoşullar ve Mimari

Gerekenler: Node.js 18+, Next.js 13+ (App Router tercihen), HTTPS (yerelde self-signed veya localhost), ve bir sunucu tarafı doğrulama paketi. Toplulukta yaygın kullanılan kütüphanelerden biri @simplewebauthn. Akış şu şekilde işler: Sunucu, kayıt veya giriş için tek kullanımlık bir challenge üretir ve istemciye döner. İstemci, tarayıcıdaki navigator.credentials API’sini (veya sarmalayıcı kütüphane) çağırarak kimlik doğrulayıcıyla işlem yapar ve imzalı yanıtı sunucuya gönderir. Sunucu, public key ve imza doğrulamasıyla işlemi sonuçlandırır.

Kurulum

Yeni bir Next.js projesi oluşturarak başlayalım:

npx create-next-app@latest passkey-demo --typescript
cd passkey-demo
npm i @simplewebauthn/server @simplewebauthn/browser cookie uuid

Basit bir yapı için .env dosyanıza aşağıdaki değerleri ekleyin (yerel geliştirme için):

RP_ID=localhost
RP_NAME=Passkey Demo
ORIGIN=http://localhost:3000

Kayıt (Registration) Akışı

Kullanıcı bir Passkey oluşturmak istediğinde önce sunucudan registration options alır. Bu seçenekler, kimlik doğrulayıcıya hangi parametrelerle anahtar üretmesi gerektiğini söyler. Next.js App Router kullandığınızı varsayarak endpoint’leri şu şekilde düşünebilirsiniz:

// app/api/webauthn/registration/options/route.ts
import { generateRegistrationOptions } from '@simplewebauthn/server';
import { cookies } from 'next/headers';
import { v4 as uuid } from 'uuid';

export async function GET() {
  const userId = uuid(); // Demo için rastgele. Gerçekte gerçek kullanıcı ID'si olmalı.
  const opts = await generateRegistrationOptions({
    rpName: process.env.RP_NAME!,
    rpID: process.env.RP_ID!,
    userID: userId,
    userName: '[email protected]',
    attestationType: 'none',
    authenticatorSelection: {
      residentKey: 'preferred',
      userVerification: 'preferred',
    },
  });

  // Challenge'ı oturumda saklayın
  cookies().set('regChallenge', opts.challenge, { httpOnly: true });

  return new Response(JSON.stringify({ userId, options: opts }), { status: 200 });
}

İstemci tarafında, tarayıcı API’si ile uğraşmayı kolaylaştırmak için @simplewebauthn/browser paketini kullanabiliriz:

// app/register/page.tsx
'use client';
import { startRegistration } from '@simplewebauthn/browser';

export default function Register() {
  const onRegister = async () => {
    const res = await fetch('/api/webauthn/registration/options');
    const { userId, options } = await res.json();
    const attRes = await startRegistration(options);
    await fetch('/api/webauthn/registration/verify', {
      method: 'POST',
      body: JSON.stringify({ userId, attRes }),
    });
    alert('Passkey oluşturuldu!');
  };
  return <button onClick={onRegister}>Passkey Oluştur</button>;
}

Sunucu, istemciden gelen yanıtı doğrulamalıdır. Doğrulama sonunda public key’i ve ilgili metadata’yı kullanıcı profiline kalıcı olarak kaydedersiniz.

// app/api/webauthn/registration/verify/route.ts
import { verifyRegistrationResponse } from '@simplewebauthn/server';
import { cookies } from 'next/headers';

export async function POST(req: Request) {
  const { userId, attRes } = await req.json();
  const expectedChallenge = cookies().get('regChallenge')?.value;

  const verification = await verifyRegistrationResponse({
    response: attRes,
    expectedChallenge,
    expectedOrigin: process.env.ORIGIN!,
    expectedRPID: process.env.RP_ID!,
  });

  if (!verification.verified) return new Response('Doğrulanamadı', { status: 400 });

  // verification.registrationInfo'dan publicKey ve credentialID'yi alıp DB'ye kaydedin
  return new Response('OK', { status: 200 });
}

Giriş (Authentication) Akışı

Girişte benzer şekilde bir challenge üretip istemciye gönderirsiniz. Kullanıcı cihazındaki Passkey ile imzalanan yanıtı sunucu doğrular. Burada kullanıcıya ait kayıtlı credentialID’lerinizi ve public key’lerinizi DB’den çekmeniz gerekir.

// app/api/webauthn/authentication/options/route.ts
import { generateAuthenticationOptions } from '@simplewebauthn/server';
import { cookies } from 'next/headers';

export async function GET() {
  const opts = await generateAuthenticationOptions({
    rpID: process.env.RP_ID!,
    userVerification: 'preferred',
  });
  cookies().set('authChallenge', opts.challenge, { httpOnly: true });
  return new Response(JSON.stringify({ options: opts }), { status: 200 });
}
// app/login/page.tsx
'use client';
import { startAuthentication } from '@simplewebauthn/browser';

export default function Login() {
  const onLogin = async () => {
    const res = await fetch('/api/webauthn/authentication/options');
    const { options } = await res.json();
    const assertion = await startAuthentication(options);
    await fetch('/api/webauthn/authentication/verify', {
      method: 'POST',
      body: JSON.stringify({ assertion }),
    });
    alert('Giriş başarılı!');
  };
  return <button onClick={onLogin}>Passkey ile Giriş</button>;
}
// app/api/webauthn/authentication/verify/route.ts
import { verifyAuthenticationResponse } from '@simplewebauthn/server';
import { cookies } from 'next/headers';

// Not: Burada kullanıcıya ait credential'ları DB'den çekmelisiniz.
export async function POST(req: Request) {
  const { assertion } = await req.json();
  const expectedChallenge = cookies().get('authChallenge')?.value;

  const verification = await verifyAuthenticationResponse({
    response: assertion,
    expectedChallenge,
    expectedOrigin: process.env.ORIGIN!,
    expectedRPID: process.env.RP_ID!,
    authenticator: {
      credentialID: Buffer.from('...'), // DB'den
      credentialPublicKey: Buffer.from('...'), // DB'den
      counter: 0,
    },
  });

  if (!verification.verified) return new Response('Hatalı kimlik doğrulama', { status: 401 });
  return new Response('OK', { status: 200 });
}

Üretime Hazırlık: İpuçları ve En İyi Uygulamalar

- HTTPS zorunlu: WebAuthn, localhost harici kökenlerde güvenli bağlam ister. Prod ortamda gerçek sertifika kullanın.

- RP ID ve Origin uyumu: RP_ID alan adınızla, ORIGIN ise protokol ve host’la birebir eşleşmeli. Alt alan adları ve çoklu çevrelerde yanlış yapılandırma en yaygın hatadır.

- Challenge ve oturum: Challenge değerini kısa ömürlü ve kullanıcıya bağlı saklayın. CSRF ve yeniden oynatma saldırılarına karşı dikkatli olun.

- Kullanıcı deneyimi: Platform authenticator (cihaz içi) ve cross-platform (USB/NFC) anahtarları desteklediğinizden emin olun. Cihaz değişimlerinde çoklu Passkey veya hesap kurtarma akışları tasarlayın.

- Günlükleme ve ölçüm: Başarısız doğrulama nedenlerini (origin uyuşmuyor, RP ID yanlış, kullanıcı doğrulaması başarısız vb.) log’layın; destek taleplerini azaltır.

Sonuç

WebAuthn ve Passkey ile parolasız kimlik doğrulama, hem saldırı yüzeyini küçültür hem de kullanıcı memnuniyetini artırır. Next.js üzerinde @simplewebauthn ile kurulum oldukça hızlıdır: kayıt ve giriş için challenge kurgusu, doğru RP/Origin ayarı ve güvenli oturum yönetimi yapı taşlarını oluşturur. Bu mimariyi uygulayarak uygulamanızda modern, phishing dirençli ve yüksek dönüşüm oranına sahip bir giriş deneyimine geçiş yapabilirsiniz.

3 Aralık 2025 Çarşamba

Next.js ile Passkey (WebAuthn) Entegrasyonu: Parolasız Girişe Adım Adım Rehber

Giriş: Neden Passkey (WebAuthn)?

Parolalar hem kullanıcılar hem de geliştiriciler için baş ağrısı. Zayıf parolalar, kimlik avı, sızıntılar ve unutma problemi derken, güvenli ve sorunsuz bir deneyim için endüstri Passkey (FIDO2/WebAuthn) standartına hızla geçiyor. Passkey ile kullanıcılar biyometri (Face ID, Touch ID), donanım güvenlik anahtarı veya cihaz PIN’iyle güvenli ve parolasız giriş yapabiliyor. Bu rehberde, Next.js ve simplewebauthn kullanarak uçtan uca bir Passkey akışını nasıl kurabileceğinizi adım adım anlatıyorum.

Gereksinimler ve Kurulum

Başlamadan önce Node.js 18+, Next.js 13/14 App Router, bir veri tabanı (PostgreSQL veya MongoDB) ve HTTPS (localhost için Chrome’da güvenilir sertifika veya bir tünel aracı) gerekli. WebAuthn, origin ve rpID değerlerine duyarlıdır; yerel geliştirmede localhost ve üretimde gerçek alan adını kullanın. Kütüphaneler için npm i @simplewebauthn/server @simplewebauthn/browser komutunu çalıştırın.

Mimari: Kayıt ve Giriş Akışı

Passkey iki ana akıştan oluşur: Kayıt (Registration) ve Doğrulama (Authentication). Kayıtta sunucu bir challenge üretir, istemci bu veriyi cihazın güvenli donanımına kaydeder ve attestation verisini sunucuya geri yollar. Girişte ise sunucu yeni bir challenge üretir, istemci cihazı bunu imzalar ve imzalı assertion sunucuda doğrulanır. Tüm süreçte challenge değerini oturum (session, Redis) gibi geçici ve güvenli bir yerde tutmak önemlidir.

Sunucu Tarafı: Endpoints ve Doğrulama

Next.js App Router’da app/api altında iki çift endpoint tanımlayın: /register/generate ve /register/verify, /login/generate ve /login/verify. @simplewebauthn/server içindeki generateRegistrationOptions, verifyRegistrationResponse, generateAuthenticationOptions ve verifyAuthenticationResponse yardımcıları işinizi hızlandırır.

Önemli alanlar: rpID alan adınız (ör. example.com), origin tam kök adresiniz (ör. https://example.com). Üretimde bunların sabit ve doğru olması gerekir. Kayıt doğrulandıktan sonra veritabanında şu alanları saklayın: kullanıcı kimliği, credential ID (base64url), publicKey, counter, transports, backupEligibility/backupState bilgileri. Girişte doğrulama sonrası karşılaştırma için counter değerini güncelleyerek yeniden oynatma saldırılarını engelleyin.

İstemci Tarafı: Tarayıcı API’leriyle Etkileşim

Tarayıcı tarafında @simplewebauthn/browser kütüphanesinin startRegistration ve startAuthentication fonksiyonlarını kullanın. Kayıt için önce /register/generate endpoint’inden seçenekleri alın, startRegistration çağrısını yapın ve sonucu /register/verify’e gönderin. Giriş için benzer şekilde /login/generate → startAuthentication → /login/verify akışını izleyin. Bu işlemler sırasında kullanıcı, cihazında biyometrik doğrulama veya güvenlik anahtarına dokunuş gibi bir etkileşimle süreci tamamlar.

Pratik İpuçları ve Sık Hatalar

- Origin/RP ID uyuşmazlığı: En yaygın hata budur. Geliştirmede https://localhost:3000 origin ile rpID=localhost kullanın. Üretimde https://alanadiniz.com ve rpID=alanadiniz.com şart.

- Challenge saklama: Challenge’ı oturumda tutmazsanız doğrulama başarısız olur. Kısa ömürlü bir store (Redis, Signed Cookie) güvenlidir.

- Discoverable credentials: Kullanıcının e-posta girmeden giriş yapabilmesi için resident keys (discoverable) açılabilir. Gizlilik ve UX dengesini iyi düşünün; bazı senaryolarda kullanıcı kimliğini yine de isteyebilirsiniz.

- Platform desteği: iOS/Android, Chrome/Safari/Edge yeni sürümlerde destekler iyi durumdadır; ancak eski cihazlarda güvenlik anahtarı (YubiKey) fallback’i planlayın.

Güvenlik ve Uyum Notları

WebAuthn, kimlik avını ciddi ölçüde azaltır; çünkü imza domain’e (origin) bağlıdır. Yine de bazı ek önlemler gerekir: her istek için CSRF koruması, üretimde HTTPS zorunluluğu, gelen yanıtlar için origin doğrulaması, güvenli cookie ayarları (HttpOnly, SameSite=Lax/Strict, Secure). Kullanıcının birden fazla cihazda passkey kullanabilmesi için hesap başına birden fazla credential kaydına izin verin ve kolay yönetim ekranları oluşturun.

Kurumsal ortamlarda enforce MFA ve politika tabanlı kayıt akışlarını düşünebilirsiniz. Ayrıca, backup/icloud passkey senkronizasyonu devredeyse, cihaz değişiminde kullanıcı deneyimi daha pürüzsüz olur. Bu arada, userVerification modunu required olarak ayarlamak, cihazda biyometrik/PIN doğrulamasını zorunlu kılarak güvenliği artırır.

Örnek Veri Modeli ve Loglama

Basit bir credential tablosu şu alanları içerebilir: id, userId, credentialId (unique), publicKey, counter, deviceType (platform/cross-platform), backupState, transports, createdAt, lastUsedAt. Her doğrulamada lastUsedAt ve counter değerlerini güncelleyin. Üretimde başarısız doğrulamalar için detaylı ancak kişisel veriyi ifşa etmeyen loglar tutun; PII saklamamaya özen gösterin.

Performans ve UX İyileştirmeleri

İlk boyamada kullanıcıya cihazın passkey desteklediğini hızlıca göstermek için PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable() ile bir özellik kontrolü yapabilirsiniz. Desteklenmiyorsa alternatif girişi (OTP/Magic Link) otomatik önerin. SSR yerine istemci tarafında akışı tetikleyip, istekleri mutate eden bir data fetching yaklaşımı (SWR/React Query) ile ağ hatalarında kullanıcıya net geri bildirim sağlayın.

Sonuç

Passkey (WebAuthn) entegrasyonu, hem güvenliği hem de dönüşüm oranlarını artıran çağdaş bir yaklaşım. Next.js ve simplewebauthn ile kurulum nispeten hızlı; kritik nokta ise rpID/origin doğruluğu, challenge yönetimi ve credential saklama stratejisi. Bu rehberdeki adımları izleyerek parolasız deneyimi projenize ekleyebilir, kullanıcılarınıza modern ve güvenli bir giriş akışı sunabilirsiniz.

25 Kasım 2025 Salı

Passkey ve WebAuthn ile Parolasız Giriş: Adım Adım Entegrasyon Rehberi (2025)

WebAuthn ve Passkey Nedir, Neden Önemli?

Parola yorgunluğu ve oltalama saldırıları, modern uygulamalar için en büyük güvenlik sorunlarından biri. WebAuthn (W3C standardı) ve FIDO2 ile gelen passkey yaklaşımı, kriptografik anahtarlar kullanarak parolasız ve oltalama dirençli oturum açmayı mümkün kılar. Kullanıcılar cihazlarındaki biyometrik doğrulama (Face ID, Touch ID, Windows Hello) veya güvenlik anahtarı (YubiKey) ile giriş yapar. 2025 itibarıyla Chrome, Safari ve Firefox, platformlar arası passkey senkronizasyonunu (iCloud Anahtarlık, Google Password Manager, 1Password) yaygın biçimde destekliyor.

Temel Kavramlar

Relying Party (RP) ID: Genellikle alan adınızdır (ör. example.com). HTTPS zorunludur ve RP ID ile domain eşleşmelidir.

Authenticator: Kimlik doğrulayıcı cihaz. Platform (cihazın kendi biyometri/TPM’i) veya roaming (USB/NFC/BLE güvenlik anahtarı) olabilir.

Attestation ve Assertion: Kayıt (credential üretimi) ve giriş (imzalı kanıt) aşamalarında tarayıcı ile sunucu arasında değiş tokuş edilen verilerin adlarıdır.

Discoverable Credentials (Resident Keys): Kullanıcının kullanıcı adı yazmadan sadece cihaz doğrulamasıyla oturum açmasına olanak tanır; passkey deneyiminin kalbidir.

Entegrasyon Mimarisi ve Akış

WebAuthn, istemci (tarayıcı) ve sunucu arasında iki ana akış tanımlar: (1) Kayıt ve (2) Giriş. Tipik uç noktalar: /webauthn/register/options (sunucu challenge üretir), /webauthn/register/verify (sunucu attestation doğrular), /webauthn/login/options (sunucu challenge üretir), /webauthn/login/verify (sunucu assertion doğrular). Sunucu tarafında kullanıcıya ait credentialID, publicKey (COSE formatında), signCount, transports ve isteğe bağlı userHandle kalıcı olarak saklanır.

Gereksinimler ve Dikkat Edilecekler

- Uygulamanız HTTPS üzerinde çalışmalı. Lokal geliştirme için localhost istisnası var, ancak üretimde sertifika zorunlu.
- RP ID, alt alan adları ile farklılık gösterebilir. Örneğin app.example.com için RP ID’yi example.com seçerseniz, alt alanlar arasında passkey paylaşımı kolaylaşır.
- Sunucuda doğru algoritma setini (ES256 gibi) destekleyin.
- Origin ve RP ID mutlak doğrulanmalı; aksi halde güvenlik modeli bozulur.
- signCount (veya signature counter) replay tespitinde kullanılmalıdır.

Uygulama Örneği: Sunucu ve İstemci

Sunucu teknolojisi fark etmeksizin yaklaşım aynıdır. Node.js için @simplewebauthn/server, tarayıcı tarafı için @simplewebauthn/browser; Java için webauthn4j; .NET için Fido2NetLib yaygın kütüphanelerdir.

Kayıt adımları: 1) Kullanıcı oturum açmış veya e-posta doğrulamış olmalı. 2) Sunucu challenge üretir, rp (name, id), user (id, name), pubKeyCredParams, authenticatorSelection (residentKey=required, userVerification=preferred/required) gibi alanlarla tarayıcıya döner. 3) Tarayıcı navigator.credentials.create({ publicKey: ... }) çağırır. 4) Tarayıcıdan dönen attestation, sunucuda doğrulanır; geçerliyse publicKey kaydedilir.

Giriş adımları: 1) Sunucu challenge üretir ve allowCredentials (istemciye bağlı ise opsiyonel) ile döner. 2) Tarayıcı navigator.credentials.get({ publicKey: ... }) çağırır. 3) Dönen assertion, imza ve authenticatorData sunucuda doğrulanır; signCount güncellenir ve oturum açılır.

Passkey UX İpuçları ve Conditional UI

Modern tarayıcılarda “Conditional UI” desteğiyle, kullanıcı adı alanı odaklanmadan dahi passkey önerisi açılabilir. Tarayıcı tarafında mediation: "conditional" kullanımı, şifre doldurma ile tutarlı bir deneyim sunar. Özellikle mobilde autofill entegrasyonu dönüşüm oranlarını artırır. “Kullanıcı adı olmadan giriş” senaryosu için “discoverable credentials” etkin olmalıdır.

Cihazlar Arası Senkronizasyon ve Kurtarma

Passkey’ler iCloud Keychain, Google Password Manager veya destekleyen parola kasalarında şifrelenmiş biçimde senkronize olabilir. Kullanıcılara en az bir roaming güvenlik anahtarı veya alternatif kurtarma yöntemi önerin. SMS/e-posta yedekleri zorunlu olmamalı; mümkünse TOTP veya ek bir passkey kaydı sağlayın.

Güvenlik ve Uyum

- Attestation politikanızı belirleyin: “none” çoğu tüketici uygulaması için yeterli, kurumsal ortamda AAGUID bazlı kısıtlama gerekebilir.
- Phishing dirençli olması, RP ID sabitlemesi ve kullanıcı doğrulaması (UV) ile sağlanır.
- Rate limiting, origin checking ve replay protection uygulayın.
- Log’larda özel anahtar yer almaz; yalnızca publicKey ve metadata saklanır.

Test, Hata Ayıklama ve Yayına Alma

Geliştirme sürecinde Chrome’un Virtual Authenticator panelini (chrome://webauthn) kullanarak farklı cihaz senaryolarını simüle edebilirsiniz. Staging ortamında gerçek cihazlarla (iOS Safari, Android Chrome, Windows Hello) çapraz test yapın. CDN veya ters proxy arkasında origin/RP ID uyuşmazlıklarına dikkat edin. Son aşamada parola ile hibrit oturum açma bir süre daha açık tutulabilir; ardından parolayı kademeli kaldırma stratejisi izlenebilir.

Sonuç

WebAuthn ve passkey, kullanıcı deneyimini iyileştirirken güvenliği ciddi biçimde artırır. Doğru RP ID, sıkı origin doğrulaması, güvenilir kütüphaneler ve iyi bir kurtarma politikası ile entegrasyon sorunsuz ilerler. Bugün küçük bir pilot başlatıp kullanıcılarınızın en çok kullandığı platformlarda test ederek geçişi adım adım hızlandırabilirsiniz.

17 Kasım 2025 Pazartesi

Next.js ile Passkey (WebAuthn) Entegrasyonu: Adım Adım Parolasız Giriş Rehberi

Giriş

Parolasız giriş, kullanıcı deneyimini iyileştirirken güvenlik risklerini ciddi şekilde azaltan modern bir yaklaşım. Passkey teknolojisi, FIDO2/WebAuthn standartları üzerine kurulu olup, kimlik doğrulamayı biyometri (Face ID, Touch ID, Windows Hello) veya cihaz PIN’i gibi yerel yöntemlere devrediyor. Bu rehberde, Next.js tabanlı bir projeye Passkey (WebAuthn) eklemenin pratik bir yolunu adım adım anlatıyorum. Amacımız, hızlıca çalışan bir kayıt (registration) ve giriş (authentication) akışı kurmak.

Neden Passkey?

Parola sızıntıları, kimlik avı (phishing) ve zayıf şifreler artık klasik güvenlik açıkları. Passkey, özel anahtarın cihazda güvenli biçimde saklanması ve sitenizin alan adına (RP ID) bağlanması nedeniyle phishing’e karşı dayanıklıdır. Kullanıcılar şifre hatırlamak zorunda kalmaz; cihazlarının biyometrik sensörleri ile tek dokunuşta oturum açabilirler.

Ön Koşullar

- Next.js 13+ (App Router önerilir), Node.js 18+
- HTTPS ortamı (yerelde localhost istisnası)
- Modern bir tarayıcı (Chrome, Edge, Safari, Firefox; mobil platformlarda da destek artıyor)
- Temel bir veritabanı (PostgreSQL, MySQL veya Prisma ile soyutlama)

Gerekli Paketler

WebAuthn işlemlerini kolaylaştırmak için yaygın olarak kullanılan @simplewebauthn paketlerini kullanacağız. Terminalde aşağıdaki komutu çalıştırın:

npm i @simplewebauthn/server @simplewebauthn/browser zod

Mimariyi Anlamak

Kayıt akışında sunucu, kullanıcı için bir “challenge” üretir ve istemci bu challenge’ı cihazdaki güvenlik anahtarında imzalayıp geri gönderir. Sunucu, gelen yanıtı doğrular ve kimlik bilgilerini (credential) veritabanına yazar. Giriş akışı benzer şekilde çalışır ancak var olan credential ile imzalama yapılır. Tüm süreçte origin (https://alanadiniz.com) ve RP ID (alanadiniz.com) uyumu kritik önemdedir.

Adım 1: Kayıt (Registration) API’si

App Router kullandığınızı varsayalım. Kayıt başlatma için “/api/webauthn/register/options” ve doğrulama için “/api/webauthn/register/verify” uç noktaları oluşturalım.

// app/api/webauthn/register/options/route.ts
import { NextResponse } from 'next/server';
import { generateRegistrationOptions } from '@simplewebauthn/server';

export async function POST() {
  const rpName = 'Uygulama Adı';
  const rpID = process.env.RP_ID || 'localhost'; // üretimde alanadiniz.com
  const user = { id: 'user-123', name: '[email protected]', displayName: 'Ali' };

  const options = await generateRegistrationOptions({
    rpName,
    rpID,
    userID: user.id,
    userName: user.name,
    timeout: 60000,
    attestationType: 'none',
    authenticatorSelection: {
      residentKey: 'preferred',
      userVerification: 'preferred',
    },
  });

  // challenge'ı oturum/cookie/cache'te saklayın
  // ör: await saveChallenge(user.id, options.challenge)

  return NextResponse.json(options);
}

Kullanıcı, tarayıcıda bu seçeneklerle passkey kaydı başlatır. Ardından istemciden dönen yanıtı doğrulamak için verify uç noktası:

// app/api/webauthn/register/verify/route.ts
import { NextResponse } from 'next/server';
import {
  verifyRegistrationResponse,
} from '@simplewebauthn/server';

export async function POST(req: Request) {
  const body = await req.json();
  const rpID = process.env.RP_ID || 'localhost';
  const expectedOrigin = process.env.EXPECTED_ORIGIN || 'http://localhost:3000';

  // const expectedChallenge = await getChallenge(userId)

  const verification = await verifyRegistrationResponse({
    response: body,
    expectedChallenge: '...challenge...',
    expectedOrigin,
    expectedRPID: rpID,
  });

  const { verified, registrationInfo } = verification;

  if (verified && registrationInfo) {
    const {
      credentialPublicKey,
      credentialID,
      counter,
      credentialBackedUp,
      credentialDeviceType,
    } = registrationInfo;

    // Veritabanına kaydedin:
    // credentialID (base64url), publicKey (Buffer), counter, userId vb.

    return NextResponse.json({ ok: true });
  }

  return NextResponse.json({ ok: false }, { status: 400 });
}

Adım 2: İstemci (Kayıt)

İstemci tarafında @simplewebauthn/browser paketini kullanın. Aşağıda basit bir örnek var:

import {
  startRegistration,
} from '@simplewebauthn/browser';

async function registerPasskey() {
  const optionsRes = await fetch('/api/webauthn/register/options', { method: 'POST' });
  const options = await optionsRes.json();

  const attResp = await startRegistration(options);
  const verifyRes = await fetch('/api/webauthn/register/verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(attResp),
  });

  if (verifyRes.ok) {
    alert('Passkey kaydedildi!');
  }
}

Adım 3: Giriş (Authentication) API’si

Girişte de benzer iki uç nokta gerekir: “/api/webauthn/auth/options” ve “/api/webauthn/auth/verify”.

// app/api/webauthn/auth/options/route.ts
import { NextResponse } from 'next/server';
import { generateAuthenticationOptions } from '@simplewebauthn/server';

export async function POST() {
  const rpID = process.env.RP_ID || 'localhost';
  // kullanıcıyı e-posta ile tespit ettiğinizi varsayın ve onun credentialID'lerini çekin
  // const allowCredentials = [...] // veritabanından
  const options = await generateAuthenticationOptions({
    rpID,
    timeout: 60000,
    userVerification: 'preferred',
    // allowCredentials,
  });

  // challenge saklanır: await saveAuthChallenge(userId, options.challenge)
  return NextResponse.json(options);
}
// app/api/webauthn/auth/verify/route.ts
import { NextResponse } from 'next/server';
import { verifyAuthenticationResponse } from '@simplewebauthn/server';

export async function POST(req: Request) {
  const body = await req.json();
  const rpID = process.env.RP_ID || 'localhost';
  const expectedOrigin = process.env.EXPECTED_ORIGIN || 'http://localhost:3000';

  // const expectedChallenge = await getAuthChallenge(userId)
  // const authenticator = await getAuthenticator(credentialID)

  const verification = await verifyAuthenticationResponse({
    response: body,
    expectedChallenge: '...challenge...',
    expectedOrigin,
    expectedRPID: rpID,
    authenticator: {
      credentialPublicKey: Buffer.from('...'),
      credentialID: Buffer.from('...'),
      counter: 0,
      transports: ['internal', 'hybrid'],
    },
  });

  const { verified, authenticationInfo } = verification;

  if (verified && authenticationInfo) {
    const { newCounter } = authenticationInfo;
    // counter'ı güncelleyin ve oturum açın (JWT/Session)
    return NextResponse.json({ ok: true });
  }

  return NextResponse.json({ ok: false }, { status: 401 });
}

Adım 4: İstemci (Giriş)

İstemci tarafında startAuthentication ile challenge’ı imzalatıp doğrulamaya gönderin.

import {
  startAuthentication,
} from '@simplewebauthn/browser';

async function loginWithPasskey() {
  const optionsRes = await fetch('/api/webauthn/auth/options', { method: 'POST' });
  const options = await optionsRes.json();

  const asseResp = await startAuthentication(options);
  const verifyRes = await fetch('/api/webauthn/auth/verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(asseResp),
  });

  if (verifyRes.ok) {
    // yönlendirme veya token işlemleri
  }
}

Veritabanı ve Güvenlik Notları

- RP ID üretimde çıplak alan adınız olmalı (ör. alanadiniz.com). Subdomain kullanıyorsanız buna dikkat edin.
- expectedOrigin tam şema ile eşleşmeli (https://alanadiniz.com). HTTP yerine HTTPS zorunludur (localhost hariç).
- Veritabanında şu alanlar saklanır: userId, credentialID (base64url), credentialPublicKey (Buffer), counter, transports, deviceType/backup bilgisi.
- Aynı kullanıcı için birden fazla credential destekleyin; kullanıcı yeni cihaz ekleyebilir.
- 0-RTT veya platform senkronizasyonu (iCloud Anahtar Zinciri, Google Password Manager) sayesinde cihazlar arası passkey geçişi mümkün hale gelir.

Sorun Giderme

- NotAllowedError: Genellikle origin veya user gesture eksikliği. Butona tıklama gibi bir kullanıcı etkileşimiyle çağırın.
- SecurityError: RP ID ile origin uyuşmuyor. Ortam değişkenlerini (RP_ID, EXPECTED_ORIGIN) kontrol edin.
- Unknown or unsupported transport: Esnek olun; transports alanını istemci döndürdüğü şekilde saklayın.
- Gömülü tarayıcılar: Bazı uygulama içi web görünümleri WebAuthn’ı kısıtlayabilir; harici tarayıcı önerin.

Sonuç

Passkey ile parolasız giriş, hem kullanıcılar hem de geliştiriciler için büyük bir kazanım. Next.js ve @simplewebauthn ile birkaç uç nokta ve doğru yapılandırma sayesinde modern, phishing’e dayanıklı ve hızlı bir kimlik doğrulama deneyimi sunabilirsiniz. Üretime geçmeden önce HTTPS, alan adı uyumu ve veritabanı bütünlüğü konularını titizlikle test etmeyi unutmayın.

8 Kasım 2025 Cumartesi

WebAuthn Passkey Entegrasyonu: Parolasız Giriş Nasıl Eklenir? (Adım Adım Rehber)

Passkey Nedir ve Neden Önemli?

Parolalar hem kullanıcılar hem de geliştiriciler için sorunlu: Zayıf şifreler, phishing saldırıları ve tekrar kullanım derken, güvenlik açıkları kaçınılmaz hale geliyor. Passkey (FIDO2/WebAuthn tabanlı parolasız kimlik doğrulama) bu tabloyu değiştiriyor. Kullanıcılar cihazlarının biyometrik doğrulamasıyla (Face ID, Touch ID, Windows Hello vb.) giriş yapıyor; sunucu tarafında ise yalnızca herkese açık anahtar saklanıyor. Bu yaklaşım phishing’e dirençli, kullanıcı deneyimi açısından zahmetsiz ve uzun vadede bakım maliyetini düşürüyor.

Mimari: Kısa Bir Genel Bakış

WebAuthn, iki ana akışa dayanır: kayıt (registration) ve giriş (authentication). Kayıt akışında sunucu, istemciye bir challenge ve parametreler gönderir; tarayıcı navigator.credentials.create() ile cihaz üzerinde bir anahtar çifti üretir ve herkese açık anahtarı sunucuya geri yollar. Giriş akışında ise sunucu challenge üretir, istemci navigator.credentials.get() ile imza alır ve sunucu bunu doğrular. Tüm süreçte parolayı ne ilettiğinizi ne de sakladığınızı düşünmeniz gerekmez.

Gereksinimler ve Dikkat Edilmesi Gerekenler

- Uygulamanızın HTTPS üzerinde çalışması gerekir (localhost hariç).
- RP ID alanı, genellikle alan adınızın köküdür (ör. example.com). Alt alanlar için doğru RP ID seçimi önemlidir.
- Tarayıcı ve platform desteği oldukça geniştir; yine de eski cihazları hedefliyorsanız geriye uygun bir strateji belirleyin.
- Passkey’lerin cihazlar arası senkronizasyonu (ör. iCloud Anahtar Zinciri, Google Password Manager) kullanıcı deneyimini iyileştirir, ancak kurumsal ortamlarda harici güven anahtarlarını da (security key) desteklemek isteyebilirsiniz.

Kayıt (Registration) Akışı: Adım Adım

1) Kullanıcı yeni hesap oluşturmak veya hesabına passkey eklemek istediğinde, sunucu bir challenge üretir ve publicKeyCredentialCreationOptions nesnesini oluşturur. Bu nesnede rp (adı ve id), user (id, name, displayName), pubKeyCredParams (COSE algoritmaları), authenticatorSelection (ör. residentKey, userVerification), timeout ve gerekiyorsa attestation alanları yer alır.

2) İstemci tarafında tarayıcıya navigator.credentials.create({ publicKey }) çağrısı yaptırılır. Kullanıcı biyometrik doğrulama ile onaylar. Tarayıcı size id, rawId, response.attestationObject ve response.clientDataJSON döndürür.

3) Sunucu bu verileri doğrular: clientDataJSON içindeki challenge, type ve origin kontrol edilir; attestationObject içinden sertifika zinciri ve anahtar bilgisi çıkarılır. Sonuçta kullanıcı kaydı için credential ID, public key ve signCount veritabanına kaydedilir.

4) Attestation politikanızı belirleyin: Çoğu tüketici uygulaması için none yeterlidir. Düzenlemeye tabi sektörlerde (finans, sağlık) cihaz kanıtı (attestation) şartları farklı olabilir.

Giriş (Authentication) Akışı: Adım Adım

1) Kullanıcı “Giriş Yap” dediğinde sunucu bir challenge üretir ve publicKeyCredentialRequestOptions oluşturur. Bu nesnede challenge, timeout, rpId ve varsa allowCredentials bulunur.

2) İstemci navigator.credentials.get({ publicKey }) çağrısı yapar. Tarayıcı kullanıcı doğrulamasını ister ve size id, rawId, response.authenticatorData, response.clientDataJSON, response.signature ve mümkünse userHandle döndürür.

3) Sunucu doğrulama yapar: clientDataJSON içindeki challenge ve origin kontrol edilir; authenticatorData içinden RP ID hash, bayraklar ve sayaç (signCount) okunur. signature, veritabanına kaydettiğiniz herkese açık anahtarla doğrulanır. Sayaç ileri gitmiyorsa tekrar kullanım şüphesiyle oturumu reddedin veya riske göre ek doğrulama isteyin.

Discoverable Credentials ve Kullanıcı Deneyimi

Passkey’lerin en güçlü yanlarından biri, discoverable credentials desteğidir. Bu sayede kullanıcı, e-posta veya kullanıcı adı girmeden doğrudan cihazındaki passkey ile giriş yapabilir. authenticatorSelection.residentKey = "required" ve userVerification = "required" ayarlarıyla modern bir “tek dokunuşla giriş” deneyimi sağlayabilirsiniz.

Güvenlik ve Uyum İpuçları

- Her istek için tek kullanımlık random challenge üretin ve kısa süreliğine geçerli kılın.
- RP ID ve origin eşleşmesini sıkı tutun; üretim ve test ortamlarını karıştırmayın.
- Rate limiting ve risk tabanlı kontroller (IP, cihaz parmak izi, olağandışı saat dilimi) ekleyin.
- Kullanıcının passkey kaybına karşı yedek faktör (TOTP, e-posta onayı, güvenlik anahtarı) sunun; ancak bu faktörleri de phishing’e dayanıklı hale getirmeyi hedefleyin.
- Veritabanınızda publicKey, credentialId, signCount, transports, aaguid gibi alanları saklayın; ileride politika kararları için faydalıdır.

Test, Hata Ayıklama ve Ölçüm

Geliştirme sırasında tarayıcının geliştirici araçlarındaki WebAuthn panelini kullanın; sanal güvenlik anahtarlarıyla senaryoları çoğaltabilirsiniz. Kayıt ve giriş akışlarında challenge ömrü, origin eşleşmesi ve base64url dönüşümleri en sık hata yapılan noktalardır. Üretimde ise başarısızlık oranı, adım terk ve cihaz/OS dağılımı gibi metrikleri izleyip sürtünme noktalarını azaltın.

Sonuç: Daha Güvenli ve Daha Hızlı Giriş

Passkey, hem güvenliği hem de kullanıcı deneyimini aynı anda iyileştiren nadir teknolojilerden biri. Parola sıfırlama süreçleri, bot saldırıları ve sahte giriş sayfalarıyla uğraşmak yerine, kökünden daha sağlam bir mimariye geçiyorsunuz. Küçük bir pilot kuvvetle başlayıp kullanıcı kitlenizin cihaz dağılımını ölçün; ardından parolasız girişe kademeli geçiş stratejisi belirleyin. Doğru uygulandığında, yardım masası yükü azalır, giriş süreleri kısalır ve dönüşüm oranları gözle görülür biçimde artar.

1 Kasım 2025 Cumartesi

Passkey (WebAuthn) ile Parolasız Giriş Nasıl Eklenir? Next.js ve SimpleWebAuthn ile Adım Adım

Passkey nedir ve neden şimdi?

Parolalar artık güvenliğin en zayıf halkası. Passkey, FIDO2 ve WebAuthn standartları ile çalışan, kimlik doğrulamayı cihazınızdaki güvenli donanım modüllerine (Secure Enclave, TPM) emanet eden parolasız bir giriş yöntemidir. Kimlik avına dayanıklıdır, şifre sızmalarından etkilenmez ve iCloud Anahtar Zinciri, Google Parola Yöneticisi ve Microsoft hesabı ile senkronize olabilir. Bu yazıda, modern bir React çerçevesi olan Next.js üzerinde SimpleWebAuthn kullanarak passkey entegrasyonunu adım adım kuracağız.

Mimariyi hızlıca anlayalım

WebAuthn iki temel akıştan oluşur: Kayıt (Registration) ve Giriş (Authentication). Sunucu “relying party” olarak bir challenge üretir, tarayıcı ve cihazınızdaki kimlik doğrulama modülü bu challenge’ı özel anahtar ile imzalar, sunucu da daha önce kaydedilen genel anahtar ile imzayı doğrular. Kritik parametreler: rpId (alan adınız), origin (https kökeniniz), user.id (kalıcı benzersiz kimlik), counter (replay saldırılarına karşı sayaç) ve userVerification (biyometrik/PIN doğrulaması). Hepsi doğru ayarlanmadığında tarayıcı güvenlik nedeniyle işlemi reddeder.

Gerekenler ve kurulum

Başlamak için Node.js 18+ ve Next.js 13+ önerilir. Örneklerde App Router kullanılacaktır. Projeyi oluşturmak için: npx create-next-app@latest passkey-demo. Ardından gerekli kütüphaneler: npm i @simplewebauthn/server @simplewebauthn/browser cookie. Basitlik için oturumları cookie ile yöneteceğiz; gerçek projede JWT veya session store tercih edebilirsiniz.

Kayıt (registration) akışı

1) Sunucu tarafı seçenek üretimi: Kullanıcı yeni bir passkey oluşturmak istediğinde generateRegistrationOptions ile istemciye seçenek döndürün. Örnek ayarlar: rpName: 'Örnek Uygulama', rpID: 'alanadiniz.com', userID: 'kalici-uuid', userName: '[email protected]', attestationType: 'none', authenticatorSelection: { residentKey: 'preferred', userVerification: 'preferred' }. Bu adımda kullanıcıya bir challenge ve uyumlu parametreler verilir.

2) İstemci tarafı oluşturma: Tarayıcıda startRegistration(options) çağrılır ve kullanıcı cihazındaki biyometrik/PIN doğrulaması tetiklenir. İşlem başarılıysa dönen attestationResponse’ı backend’e gönderirsiniz.

3) Doğrulama ve kaydetme: Sunucuda verifyRegistrationResponse ile origin ve rpID doğrulanır. Başarılıysa veritabanına şu alanları kaydedin: credentialId (Base64URL), publicKey, counter, userId, transports, tercihen aaguid ve device type. Bu kayıt, o kullanıcının passkey’inin “kamu anahtarını” temsil eder.

Giriş (authentication) akışı

1) Seçenek üretimi: generateAuthenticationOptions ile istemciye yeni bir challenge gönderin. Eğer sadece belirli kayıtlı kimlik bilgilerine izin vermek isterseniz allowCredentials içinde credentialId’leri gönderin. Parolasız deneyim için discoverable credentials (residentKey) tercih edilir.

2) İstemci doğrulaması: Tarayıcıda startAuthentication(options) çağrılır. Kullanıcı biyometrik/PIN ile onaylar, ortaya çıkan assertionResponse sunucuya gönderilir.

3) Sunucu doğrulaması: verifyAuthenticationResponse ile imza doğrulanır; challenge, origin, rpId ve counter kontrol edilir. Sayaç değeri yükseldiyse veritabanındaki counter güncellenir. Başarılıysa kullanıcıyı oturum açmış sayıp güvenli bir Set-Cookie ile session başlatın.

Üretim için kritik güvenlik ayarları

- HTTPS zorunludur. WebAuthn yalnızca güvenli kökenlerde çalışır (localhost hariç). Cloudflare, Vercel veya kendi Nginx ters proxy’nizle TLS sağlayın.

- rpID tam alan adınız olmalı ve origin ile uyumlu olmalı. Örn. app.ornek.com kullanıyorsanız http:// değil https://app.ornek.com üzerinden test edin.

- userVerification’ı en az preferred, kritik işlemlerde required yapın; böylece biyometri/PIN doğrulaması zorunlu olur.

- Attestation çoğu senaryoda none kalabilir. Cihaz sınıfı kontrolü istiyorsanız indirect veya direct kullanabilir, gizlilik etkilerini değerlendirmelisiniz.

- Orta katmanda rate limiting, CSRF koruması ve replay önlemleri (tek kullanımlık challenge, kısa süreli TTL) uygulayın.

Tarayıcı ve cihaz uyumluluğu

Güncel Chrome, Safari ve Edge sürümleri passkey’i destekliyor. iOS ve Android cihazlarda platform authenticator ile sorunsuz çalışır. Masaüstünde “telefonla doğrula” akışı (QR/BT) mümkün. Kullanıcılara “fiziksel güvenlik anahtarı” (YubiKey) ile ek kayıt yaptırmak da kurtarma planı olarak önerilir.

Sık karşılaşılan hatalar

NotAllowedError: Kullanıcı etkileşimi (click) yoksa veya işlem zaman aşımına uğradıysa görülür. İşlemleri buton tıklamasıyla başlatın ve timeout değerini 60–120 sn yapın.

InvalidStateError: Aynı cihazda aynı kullanıcı için tekrar kayıt deneniyorsa oluşabilir. Kayıt öncesi kullanıcıya mevcut passkey’i kaldırma veya yeni cihaz olarak ekleme seçenekleri sunun.

RP ID hatası: “The RP ID is not a registrable domain” mesajı alıyorsanız muhtemelen localhost dışı http kullanıyorsunuzdur. Her zaman https kullanın ve alan adınızın kökeni ile rpID’yi eşleştirin.

Sonuç ve öneriler

Passkey entegrasyonu hem güvenliği hem de dönüşümü artırır: kullanıcılar parolaları hatırlamak zorunda kalmaz, giriş süresi kısalır. Next.js ve SimpleWebAuthn ikilisiyle temel kurulum birkaç saat içinde tamamlanabilir. Üretimde çoklu cihaz desteği, kurtarma seçenekleri (ikinci passkey, e-posta destekli destek süreci), denetim kayıtları ve ayrıntılı telemetriyi (başarı/başarısızlık oranları, tarayıcı/cihaz dağılımı) planlayın. Parolasız geleceğe adım atmak için bugün küçük bir pilotla başlayabilir, kullanıcılarınızdan gelen geri bildirimlere göre akışı iyileştirebilirsiniz.

9 Ekim 2025 Perşembe

Passkey (FIDO2/WebAuthn) ile Parolasız Giriş: Modern Web Uygulamalarına Adım Adım Entegrasyon

Passkey ve WebAuthn Nedir, Neden Önemli?

Parolasız kimlik doğrulama, güvenlik ve kullanıcı deneyimi açısından modern webin en kritik dönüşümlerinden biri. Passkey teknolojisi, FIDO2 ve WebAuthn standartlarına dayanarak şifreleri tamamen ortadan kaldırır ve herkese açık anahtar kriptografisiyle phishing saldırılarını kökten azaltır. Kullanıcılar, platformlarının (iOS, Android, Windows, macOS) yerleşik güvenlik donanımını veya güvenlik anahtarlarını (YubiKey gibi) kullanarak tek dokunuşla giriş yapar. Bu makalede, bir web uygulamasına passkey (WebAuthn) entegrasyonunu adım adım, pratik ipuçlarıyla anlatıyorum.

WebAuthn Nasıl Çalışır? (Kısa Teknik Özet)

WebAuthn, tarayıcı üzerinden navigator.credentials API’si ile çalışır. Kayıt (registration) aşamasında sunucu bir challenge üretir ve tarayıcıya gönderir. Kullanıcının cihazı benzersiz bir anahtar çifti (özel/public) oluşturur; public key sunucuya kaydedilir. Giriş (authentication) aşamasında sunucu yine bir challenge yollar; cihaz, özel anahtarla bu challenge’ı imzalar ve tarayıcı imzayı sunucuya iletir. Sunucu public key ile imzayı doğruladığında kullanıcı giriş yapar. Şifre yoktur; dolayısıyla credential stuffing ve klasik phishing riskleri dramatik biçimde azalır.

Ön Koşullar ve Mimarinin Temelleri

Entegrasyon için HTTPS zorunludur; rpId (Relying Party ID) genellikle alan adınızın eTLD+1 biçimidir (örneğin example.com). Backend tarafında kayıt ve giriş için iki uç nokta gerekir: /webauthn/register ve /webauthn/login (veya benzer adlar). Bu uç noktalar challenge üretir, tarayıcıdan dönen yanıtları doğrular ve kimlik bilgilerini (credentialId, publicKey, aaguid vb.) saklar. Sunucu tarafında dilinize göre bir WebAuthn/FIDO2 kütüphanesi kullanmanız önerilir; örneğin Node.js için SimpleWebAuthn, .NET için Fido2NetLib, Go için popüler WebAuthn paketleri mevcuttur.

Adım 1: Kayıt (Registration) Akışı

1) Sunucu, kullanıcı hesabı oluşturulurken bir kayıt challenge’ı üretir ve tarayıcıya dönüş yapar. Dönen JSON içinde publicKey alanı olmalıdır (rp, user, challenge, pubKeyCredParams gibi).

2) Tarayıcı tarafında kayıt isteği şu şekilde tetiklenir: const cred = await navigator.credentials.create({ publicKey: options });. Buradaki options, sunucunun verdiği PublicKeyCredentialCreationOptions içeriğidir.

3) Dönen credential, id, rawId, response.attestationObject ve response.clientDataJSON içerir. Bu verileri JSON’a uygun biçimde base64url kodlayıp sunucuya gönderin: POST /webauthn/register/verify.

4) Sunucu, attestation politikasına (genellikle attestation: "none") göre doğrulama yapar, public key ve credentialId’yi kullanıcıyla eşleştirip veritabanına kaydeder. COSE algoritması olarak ES256 varsayılan ve yaygın bir tercihtir.

Adım 2: Giriş (Authentication) ve Passkey Deneyimi

1) Giriş aşamasında sunucu bir doğrulama challenge’ı üretir ve tarayıcıya yollar. Eğer discoverable credentials (kullanıcı adı girmeden hesap seçimi yapılabilen passkey’ler) destekliyorsanız, allowCredentials alanını boş bırakabilirsiniz.

2) Tarayıcı, aşağıdaki çağrıyla kullanıcıdan biyometrik/doğrulama isteği alır: const assertion = await navigator.credentials.get({ publicKey: requestOptions });. Modern tarayıcı ve platformlarda, mediation: "conditional" ile otomatik doldurma benzeri akış sağlayabilirsiniz: navigator.credentials.get({ publicKey: requestOptions, mediation: "conditional" }).

3) Dönen yanıtta response.authenticatorData, response.clientDataJSON ve response.signature bulunur. Bu verileri sunucuya gönderin: POST /webauthn/login/verify. Sunucu, ilgili public key ile imzayı doğrular, signCount değerini günceller ve oturumu başlatır.

En İyi Uygulamalar ve UX İpuçları

Passkey deneyimi, kullanıcı adı ve şifre kutularını tamamen devre dışı bırakabilir. Özellikle Conditional UI desteği ile (Chrome, Android, iOS ve Safari’nin güncel sürümleri) sayfa yüklenir yüklenmez platform, uygun passkey’leri önerir. Kullanıcı tek dokunuşla giriş yapar. Formlarınızda “Passkey ile giriş yap” butonunu net biçimde gösterin; ilk kurulumda parolayı da kabul edip passkey oluşturma adımını teşvik ederek geçişi yumuşatın.

Hesap kurtarma için e-posta doğrulama, yedek güvenlik anahtarları veya müşteri destek süreçleri tasarlayın. Passkey’ler iCloud veya Google hesabı üzerinden cihazlar arasında senkronize olabilir; bu özellik, kullanıcı değişen cihazlarda bile sorunsuz giriş deneyimi sağlar.

Güvenlik ve Uyum Detayları

- HTTPS zorunludur; localhost geliştirme ortamında tarayıcılar özel izinler sunar. Üretimde mutlaka geçerli bir TLS sertifikası kullanın ve TLS 1.2+ desteğini aktif tutun.

- rpId, cookie SameSite ve Secure bayrakları, Origin eşleşmeleri gibi detaylar önemlidir. Ters proxy (Nginx/Caddy/Cloudflare) arkasında header’ların doğru yönlendirildiğinden emin olun.

- Attestation politikanızı iş gereksinimlerine göre belirleyin. Çoğu SaaS için none yeterlidir; regülasyonlu sektörlerde direct veya enterprise gerekebilir.

- Zaman senkronizasyonu (NTP) ve sunucu saat farkı doğrulamada rol oynar; bu nedenle saat kaymasını minimal tutun.

Test, Hata Ayıklama ve Yayına Alma

Geliştirme sırasında tarayıcı konsolunda Security ve Network sekmelerini izleyin; WebAuthn hataları genellikle açıklayıcıdır. localhost üzerinde test ederken rpId olarak “localhost” kullanın. İlk yayında, kademeli açılış (feature flag) ile kullanıcıların küçük bir yüzdesine passkey’i sunup metrikleri takip etmek iyi bir stratejidir: başarı oranı, iptal oranı, geri dönüşler ve destek talepleri ölçülmelidir.

Sonuç: Parolasız Geleceğe Bugünden Geçin

Passkey (FIDO2/WebAuthn) entegrasyonu; daha güvenli, daha hızlı ve daha kullanıcı dostu bir oturum açma deneyimi sağlar. Şifre sıfırlama maliyetlerini düşürür, hesap ele geçirme vakalarını azaltır ve mobil cihazlarda tek dokunuşla giriş imkânı sunar. Bu rehberdeki adımları izleyerek, önce hibrit (parola + passkey) modeliyle başlayabilir, ardından kullanıcılarınız hazır olduğunda tamamen parolasız bir geleceğe geçebilirsiniz.

8 Ekim 2025 Çarşamba

Next.js Projenize Passkey (WebAuthn) ile Parolasız Giriş Ekleme: SimpleWebAuthn ile Adım Adım

Parolasız kimlik doğrulama, hem güvenliği hem de kullanıcı deneyimini aynı anda iyileştiren güncel bir yaklaşım. Bu yazıda Next.js tabanlı bir web uygulamasına Passkey (FIDO2/WebAuthn) ile giriş özelliğini nasıl ekleyebileceğinizi, SimpleWebAuthn kütüphanesinden yararlanarak adım adım anlatıyorum. Amaç; phishing’e dayanıklı, cihazlar arası senkronize olabilen ve kullanıcıya tek dokunuşla giriş sağlayan bir çözüm kurmak.

Passkey nedir? Kısaca, kullanıcı adı-parola yerine cihazın güvenli donanımında (veya güvenli modülde) saklanan anahtar çifti ile giriş yapma yöntemidir. Tarayıcı ve cihaz (ör. iPhone, Android, Windows Hello, güvenlik anahtarı) iş birliğiyle çalışır ve WebAuthn standardını uygular.

Gereksinimler ve Kurulum

Başlamadan önce Node.js 18+, Next.js 13+ (App Router veya Pages), HTTPS (geliştirme için localhost’ta istisna), ve bir veritabanına (PostgreSQL, MongoDB, vb.) ihtiyacınız var. Sunucuda SimpleWebAuthn’in server paketi, istemcide ise browser paketi kullanılacak.

npm install @simplewebauthn/server @simplewebauthn/browser

Prod ortamında RP ID (Relying Party ID) alan adınız olmalı (ör. example.com). RP Origin ise protokol + alan adıdır (ör. https://example.com). Local geliştirmede rpID “localhost”, origin ise “http://localhost:3000” olabilir.

Kayıt (Registration) Akışı

Kullanıcı yeni bir passkey oluştururken, sunucu bir “challenge” üretir ve tarayıcıya kayıt seçeneklerini (PublicKeyCredentialCreationOptions) gönderir. Tarayıcı, cihazın güvenli modülünde anahtar çifti oluşturur ve sonucu sunucuya geri yollar. Sunucu, yanıtı doğrular ve kimlik bilgisini (credential) veritabanına kaydeder.

1) Sunucu: Kayıt seçeneklerini üret

generateRegistrationOptions({ rpName, rpID, userID, userName, attestationType: 'none', authenticatorSelection: { residentKey: 'preferred', userVerification: 'preferred' } })

Burada userID rastgele ve stabil bir kimlik olmalı (ör. UUID). Üretilen challenge değerini oturumda saklayın. rpID, alan adınızla birebir uyumlu olmalıdır; alt alan adı farkları doğrulamayı bozabilir.

2) İstemci: navigator.credentials.create()

const att = await startRegistration(options)

@simplewebauthn/browser içindeki startRegistration, tarayıcı API’sini sarmalar. Kullanıcı Touch ID/Face ID/Pin ile onay verir.

3) Sunucu: Kayıt doğrulama

verifyRegistrationResponse({ response, expectedChallenge, expectedOrigin, expectedRPID })

Doğrulama başarılıysa credentialId, publicKey, signCount gibi alanları kullanıcıya bağlı şekilde veritabanınıza kaydedin. Aynı kullanıcıya birden fazla credential tanımlanabilir; cihaz değiştirme senaryoları için faydalıdır.

Giriş (Authentication) Akışı

Giriş için süreç benzer: Sunucu challenge üretir, istemci bir imza üretir, sunucu imzayı doğrular. Burada kullanılacak seçenekler PublicKeyCredentialRequestOptions’tır.

1) Sunucu: Giriş seçenekleri

generateAuthenticationOptions({ rpID, userVerification: 'preferred', allowCredentials: [{ id: credentialId, type: 'public-key' }] })

allowCredentials ile ilgili kullanıcının credentialId’lerini gönderebilirsiniz. Challenge değeri yine oturumda tutulmalı.

2) İstemci: navigator.credentials.get()

const assertion = await startAuthentication(options)

Kullanıcı cihazında biyometrik/doğrulama adımı gerçekleşir ve tarayıcı imzalı yanıtı döner.

3) Sunucu: Giriş doğrulama

verifyAuthenticationResponse({ response, expectedChallenge, expectedOrigin, expectedRPID, authenticator: storedCredential })

Başarılı doğrulamada signCount artışı izlenmeli. Beklenmedik düşüş, olası klonlanmış bir anahtara işaret edebilir.

Next.js’te İstemci Tarafı ve Oturum

Next.js’te App Router kullanıyorsanız /app/api altında iki rota belirlemek (register ve login için “options” ve “verify”) işleri düzenli kılar. İstemci tarafında butona tıklanınca önce “options” endpoint’ine istek atın, ardından dönen opsiyonları startRegistration veya startAuthentication fonksiyonuna verin. Doğrulama başarılıysa sunucudan bir HttpOnly, Secure cookie (ör. JWT veya session id) ayarlayarak oturumu başlatın. Cookie için SameSite=Strict ve kısa ömür tercih edin.

Güvenlik ve Üretim İpuçları

- Origin ve rpID eşleşmesine çok dikkat edin. Port, protokol ve alt alan adı hataları en yaygın sorundur.
- Attestation genellikle “none” seçilir; cihaz tedarikçi doğrulaması gerektiren kurum uygulamalarında “direct” düşünülebilir.
- Kullanıcılara birden fazla passkey kaydetme imkânı verin. Telefon kaybında erişim kapanmasın.
- SSRF, CSRF ve rate limit gibi genel güvenlik önlemlerini ihmal etmeyin.
- Üretimde her zaman HTTPS kullanın; WebAuthn birçok tarayıcıda güvenli bağ zorunludur.

Hata Ayıklama ve Uyumluluk

Chrome, Safari ve Firefox’un güncel sürümlerinde passkey desteği olgunlaştı. Android 14 ve iOS 17 ile birlikte platform passkey’leri cihazlar arası senkronize edilebiliyor. Test sırasında about://webauthn (Chrome) üzerinden sanal güvenlik anahtarı oluşturabilir, geliştirici seçenekleriyle akışı simüle edebilirsiniz. Hata alırsanız tarayıcı konsolundaki NotAllowedError, SecurityError ve InvalidStateError mesajları size yön gösterecektir.

Sonuç

Passkey, modern web uygulamaları için güçlü ve kullanıcı dostu bir kimlik doğrulama yöntemi. Next.js ve SimpleWebAuthn ile birkaç endpoint yazarak hızlıca devreye alabilirsiniz. Doğru rpID/origin ayarı, güvenli cookie yönetimi ve çoklu credential desteği ile hem güvenlik ekibinizi hem kullanıcılarınızı mutlu edecek bir parolasız giriş deneyimi sunabilirsiniz.

10 Eylül 2025 Çarşamba

Passkey (FIDO2/WebAuthn) ile Parolasız Giriş: Adım Adım Kurulum ve En İyi Uygulamalar

Passkey nedir ve neden şimdi?

Şifreler tarihsel olarak en zayıf halkaydı: yeniden kullanım, kimlik avı, veri sızıntıları ve karmaşa. Passkey, FIDO2 ve WebAuthn standartlarını temel alarak parolayı tamamen devreden çıkarır. Kullanıcı, cihazındaki biyometrik sensör (Face ID, Touch ID, Windows Hello) veya donanımsal güvenlik anahtarıyla (YubiKey vb.) kimlik doğrular; site ise yalnızca herkese açık anahtarı saklar. Bu yaklaşım, kimlik avına dayanıklı, hızlı ve kullanıcı dostu bir deneyim sunar.

Mimari: Nasıl çalışır?

Passkey, tarayıcı ve işletim sistemi ile cihazdaki “Authenticator” (platform ya da harici) arasında köprü kuran WebAuthn API’sini kullanır. Sunucu tarafında “Relying Party” (RP) olarak adlandırılan uygulamanız, kullanıcıyı kaydederken ve oturum açarken kriptografik bir “challenge” üretir. Tarayıcı, navigator.credentials üzerinden bu challenge’ı imzalatır ve sonucu sunucuya gönderir. Sunucu, imzayı doğrular ve kullanıcıyı oturum açmış kabul eder. Bu süreçte parola yoktur; saklanan tek kalıcı bilgi, cihazın herkese açık anahtarı ve kimlikleyici meta verileridir.

Ön koşullar ve gereksinimler

- HTTPS zorunludur. Lokal geliştirme için localhost istisnadır, üretimde sertifikanızın düzgün kurulu olması gerekir.
- Modern tarayıcı desteği (Chrome, Edge, Safari, Firefox) yaygınlaşmıştır; mobil işletim sistemlerinde de yerleşik destek mevcuttur.
- Alan adı ve rpId uyumu: Örneğin uygulamanız auth.example.com’da ise RP ID’nizi example.com veya ilgili alt alan adıyla uyumlu ayarlayın.

Uygulama seçenekleri

1) Sıfırdan uygulama: Sunucu dilinize uygun bir WebAuthn kütüphanesi kullanın. Node.js için @simplewebauthn/server, Go için duo-labs/webauthn, Python için webauthn, Java için webauthn4j gibi olgun projeler mevcuttur. Bu kütüphaneler, challenge üretimi, attestation/assertion doğrulaması ve anahtar formatlarını sizin yerinize yönetir.

2) Kimlik sağlayıcıları: Auth0, Azure AD B2C, Okta, Firebase gibi servisler passkey desteğini hızla entegre etmenizi sağlar. Bu yol, güvenlik ve mevzuat tarafında işleri kolaylaştırırken, özelleştirme esnekliğini sınırlayabilir.

3) CMS ve hazır platformlar: Bazı modern CMS’ler ve e-ticaret eklentileri passkey desteği sunmaya başladı. Kod yazmadan aktivasyon yapmak mümkün olsa da RP ID, yedek yöntemler ve çok cihaz desteği gibi ayarları doğru yapılandırdığınızdan emin olun.

Adım adım temel kurulum

1) Kullanıcı modeli: Veritabanında kullanıcıya kalıcı bir benzersiz tanımlayıcı (user.id) atayın. E-posta/telefon gibi iletişim bilgilerini tutun; kurtarma senaryoları için gerekecektir.

2) Kayıt (registration) başlangıcı: /webauthn/register/options benzeri bir uç nokta üzerinden, kullanıcı için publicKeyCredentialCreationOptions oluşturun. Parametrelerde rpId, challenge (kısa ömürlü, tek kullanımlık), user bilgisi, algoritmalar (ES256 varsayılan), residentKey ve userVerification politikalarını belirleyin.

3) İstemci tarafı kayıt: Tarayıcıda navigator.credentials.create() çağrısı yapılır. Kullanıcı, biyometri veya güvenlik anahtarıyla onay verir. Dönen PublicKeyCredential nesnesini sunucuya gönderirsiniz.

4) Sunucu doğrulaması: Attestation verisini kontrol edin (origin, rpIdHash, challenge eşleşmesi, algoritmalar). Gerekirse attestation’ı “none” kabul ederek gizliliği artırın. Sonuçta credentialId, publicKey ve signCount değerlerini veritabanına kaydedin.

5) Giriş (authentication) başlangıcı: /webauthn/authenticate/options ile publicKeyCredentialRequestOptions üretin. İlgili kullanıcı için kayıtlı kimlikleyicileri gönderin ya da kullanıcı adı olmadan (discoverable credentials) giriş akışını destekleyin.

6) İstemci tarafı giriş: navigator.credentials.get() çağrısıyla imzalanmış assertion elde edilir ve sunucuya gönderilir.

7) Sunucu doğrulaması ve oturum: İmzayı publicKey ile doğrulayın, signCount artışını kontrol edin (geri sarma tespitleri için). Başarılıysa oturum oluşturun veya JWT üretin. Risk tabanlı ek doğrulamalar (cihaz parmak izi, IP, hız limiti) bu aşamada değerlendirilebilir.

UX ve güvenlik için en iyi uygulamalar

- Kullanıcıyı bilgilendirin: “Passkey, cihazınızda güvenle saklanır ve kimlik avına dayanıklıdır” gibi net açıklamalar ekleyin. İlk oturum açmada kısa bir tur (tooltip) deneyimi dönüşümü artırır.

- Çok cihaz desteği: Platform (telefon/laptop) ve harici (NFC/Bluetooth/USB) anahtarlarla kayıt izni verin. Böylece cihaz kaybında erişim riski azalır.

- Kurtarma stratejisi: En az bir yedek yöntem tanımlayın (destek bileti, güvenlik sorularından kaçının; e-posta veya TOTP daha güvenlidir). İş kritik ortamlarda yönetici onayı veya kimlik doğrulama akışıyla destekleyin.

- Gizlilik ve attestation: Çoğu tüketici uygulaması için attestation “none” politikası yeterlidir. Regülasyon gerektiren ortamlarda üretici sertifikalarını doğrulayan bir güven zinciri kullanın.

- RP ID ve alt alan adları: Oturum açma alan adınız ile uygulama alan adınız farklıysa, rpId ayarını yanlış yapılandırmak en yaygın hatalardandır. Tarayıcı hataları “rpId mismatch” olarak döner.

- Performans: Challenge süre sonunu kısa (örn. 60–120 sn) tutun, CDN önbelleğini bu uç noktalardan uzak tutun. Mobil tarayıcıların arka plan kısıtlarına dikkat edin.

Test ve sorun giderme

- Sanal kimlikleyici: Chrome DevTools > More tools > Virtual authenticator environment ile local test yapın; farklı alg, UV politikaları ve resident key senaryolarını simüle edin.

- Uçtan uca test: WebAuthn.io gibi topluluk araçlarıyla doğrulama akışınızı karşılaştırın. Farklı tarayıcı ve platformlarda (iOS, Android, Windows, macOS) gerçek cihaz testleri yapın.

- Yaygın hatalar: NotAllowedError (kullanıcı iptali veya zaman aşımı), InvalidStateError (mevcut credential ile tekrar kayıt), COSE algoritma uyumsuzluğu (ES256 önerilir) ve saat farkları (server/client saat uyuşmazlığı) sık karşılaşılır. Log’larda challenge-id eşleşmesini ve origin değerini mutlaka izleyin.

Sonuç

Passkey, hem güvenlik hem de kullanıcı deneyimi açısından parolaların doğal halefidir. Doğru RP ID yapılandırması, sağlam bir kurtarma stratejisi ve tarayıcılar arası test ile haftalar içinde üretime alınabilir. İster hazır bir kimlik sağlayıcısıyla hızlıca başlayın, ister kendi WebAuthn katmanınızı inşa edin; passkey, kimlik avına dayanıklı, hızlı ve modern bir giriş deneyimini standart hale getirir.

4 Eylül 2025 Perşembe

Passkey (WebAuthn) ile Parolasız Giriş: Node.js ve SimpleWebAuthn ile Adım Adım Entegrasyon

Passkey (WebAuthn) ile Parolasız Giriş: Neden ve Nasıl?

Parolasız giriş dünyasında Passkey teknolojisi (WebAuthn/FIDO2) hızla standart hâline geliyor. Kimlik avı direnci, cihazlar arası senkronizasyon ve hızlı kullanıcı deneyimi sayesinde klasik parolaların yerini alıyor. Bu yazıda, Node.js ve @simplewebauthn kütüphanesini kullanarak bir web uygulamasına Passkey tabanlı kimlik doğrulamayı nasıl entegre edeceğinizi adım adım anlatıyorum. Rehber, modern tarayıcılar (Chrome, Safari, Edge) ve platformlar (iOS, Android, macOS, Windows) ile uyumludur.

Ön Koşullar ve Genel Mimari

Başlamadan önce Node.js 18+ sürümü, HTTPS ile çalışan bir alan adı (localhost için self-signed sertifika), temel Express.js bilgisi ve veri saklamak için bir veritabanı (ör. PostgreSQL, MongoDB veya basit bir bellek deposu) gerekli. Mimari olarak iki akış vardır: Kayıt (Registration) ve Giriş (Authentication). Kayıtta sunucu, kullanıcıya bir challenge üretir ve tarayıcı navigator.credentials.create() ile güvenli bir anahtar çifti oluşturur. Girişte ise sunucu yeni bir challenge üretir ve tarayıcı navigator.credentials.get() ile imza üretip doğrulatır.

Proje Kurulumu

mkdir passkey-demo && cd passkey-demo komutlarıyla klasörü oluşturun. Ardından npm init -y ve npm i express @simplewebauthn/server cors cookie-session komutlarını çalıştırın. Geliştirme için npm i -D typescript ts-node @types/express @types/cookie-session ekleyebilirsiniz. HTTPS için bir ters proxy ya da self-signed sertifika kullanın; WebAuthn çoğu senaryoda güvenli köken (https) ister.

Sunucu Tarafı: Temel Ayarlar

Express uygulamasında kök alan adınızı rpID olarak tanımlayın (ör. example.com). origin değeri tam protokol ve alan adını içermeli (ör. https://example.com). Kullanıcı oturumunda veya Redis gibi bir depoda challenge saklayın.

import express from 'express';
import { generateRegistrationOptions, verifyRegistrationResponse, generateAuthenticationOptions, verifyAuthenticationResponse } from '@simplewebauthn/server';
import session from 'cookie-session';

const app = express();
app.use(express.json());
app.use(session({ name: 'sess', keys: ['secret'], maxAge: 600000 }));

const rpID = 'example.com';
const origin = 'https://example.com';

Kayıt Akışı (Registration)

1) Options uç noktası: Kullanıcı kayıt olurken sunucu challenge üretir ve istemciye gönderir. Kullanıcı tanımlayıcısını (user.id) kalıcı bir değerden oluşturun.

app.post('/register/options', async (req, res) => {
  const { username, displayName } = req.body;
  const userId = 'user-' + username; // Örnek amaçlı
  const options = await generateRegistrationOptions({
    rpName: 'Passkey Demo',
    rpID,
    userID: userId,
    userName: username,
    userDisplayName: displayName || username,
    attestationType: 'none',
  });
  req.session.challenge = options.challenge;
  res.json(options);
});

2) Doğrulama uç noktası: İstemci, navigator.credentials.create() sonucunu bu uç noktaya gönderir. Sunucu doğrular, credential’ı veritabanına kaydeder.

app.post('/register/verify', async (req, res) => {
  const body = req.body; // client response
  const expectedChallenge = req.session.challenge;
  const verification = await verifyRegistrationResponse({
    response: body,
    expectedChallenge,
    expectedOrigin: origin,
    expectedRPID: rpID,
  });
  if (!verification.verified) return res.status(400).json({ ok: false });
  // credential kaydet: id, publicKey, counter, transports
  res.json({ ok: true });
});

Giriş Akışı (Authentication)

1) Options uç noktası: Sunucu, kullanıcıya bağlı mevcut credential’lara göre allowCredentials ile bir challenge üretir.

app.post('/login/options', async (req, res) => {
  const { username } = req.body;
  const userCreds = await loadUserCredentials(username); // DB'den çekin
  const options = await generateAuthenticationOptions({
    rpID,
    allowCredentials: userCreds.map(c => ({ id: c.id, type: 'public-key' })),
  });
  req.session.challenge = options.challenge;
  res.json(options);
});

2) Doğrulama uç noktası: İstemciden gelen imzayı doğrulayın, sayaç değerini güncelleyin ve oturumu başlatın.

app.post('/login/verify', async (req, res) => {
  const body = req.body;
  const expectedChallenge = req.session.challenge;
  const user = await findUserByCredentialId(body.rawId);
  const verification = await verifyAuthenticationResponse({
    response: body,
    expectedChallenge,
    expectedOrigin: origin,
    expectedRPID: rpID,
    authenticator: user.authenticator, // publicKey & counter
  });
  if (!verification.verified) return res.status(401).json({ ok: false });
  // counter güncelle, session başlat
  res.json({ ok: true });
});

İstemci Tarafı: WebAuthn API Kullanımı

Kayıt sırasında sunucudan aldığınız PublicKeyCredentialCreationOptions nesnesini navigator.credentials.create() içine verin. Tarayıcı, platform anahtarı (ör. iCloud Anahtar Zinciri, Google Password Manager) veya güvenlik anahtarı (YubiKey) ile cihaz üzerinde anahtar üretir.

const opts = await fetch('/register/options', { method: 'POST', body: JSON.stringify({ username }) }).then(r => r.json());
const cred = await navigator.credentials.create({ publicKey: opts });
await fetch('/register/verify', { method: 'POST', body: toJSON(cred) });

Girişte navigator.credentials.get() çağrısı yapılır. Sunucunun sağladığı PublicKeyCredentialRequestOptions ile imza üretilir ve doğrulama uç noktasına gönderilir.

const opts = await fetch('/login/options', { method: 'POST', body: JSON.stringify({ username }) }).then(r => r.json());
const assertion = await navigator.credentials.get({ publicKey: opts });
await fetch('/login/verify', { method: 'POST', body: toJSON(assertion) });

Test, Hata Ayıklama ve Uyumluluk

Geliştirmede https zorunludur; aksi takdirde tarayıcı çağrıları reddedebilir. Mobil cihazlarda test için aynı ağda çalışan https bir endpoint kullanın veya tünelleme (ngrok, Cloudflare Tunnel) tercih edin. Safari’de rpID uyumsuzluğu sık görülür; alan adınız ile origin’iniz birebir eşleşsin. Hata mesajlarını ayrıntılarıyla log’layın; özellikle challenge uyuşmazlığı, origin hatası ve RP ID hataları en yaygın sorunlardır.

Güvenlik ve UX İpuçları

- Challenge değerlerini tek kullanımlık ve kısa ömürlü saklayın; oturum veya Redis idealdir.

- Kullanıcı başına birden fazla credential kaydına izin verin; cihaz değişimlerinde deneyimi iyileştirir.

- resident key ve user verification politikalarını ihtiyaca göre ayarlayın; güçlü güvenlik için required tercih edin.

- Geriye dönük uyumluluk için geçici olarak sihirli bağlantı (magic link) veya tek kullanımlık kodları sunabilirsiniz.

- Üretimde anahtar materyalini ve sayaç değerlerini güvenilir bir veritabanında şifreli saklayın; yedeklemeleri planlayın.

Sonuç

Passkey (WebAuthn/FIDO2) ile parolasız giriş, hem güvenliği hem de kullanıcı deneyimini ileriye taşır. Node.js ve @simplewebauthn ile kurulum birkaç uç nokta ve doğru yapılandırma ile tamamlanabilir. Doğru rpID/origin eşleşmesi, güvenli challenge yönetimi ve çoklu credential desteği ile modern, kimlik avına dayanıklı ve hızlı bir giriş akışı elde edersiniz. Ürününüz büyüdükçe; cihaz senkronizasyonu, kurtarma stratejileri ve kurumsal güvenlik anahtarları ile çözümü olgunlaştırabilirsiniz.