30 Kasım 2025 Pazar

Passkey (WebAuthn) ile Şifresiz Giriş: Next.js ve SimpleWebAuthn ile Uçtan Uca Kurulum

Passkey (WebAuthn) ile Şifresiz Giriş: Adım Adım Uygulama Rehberi

Parola yorgunluğu, kimlik avı ve SMS tabanlı doğrulamanın zayıflıkları derken, modern web uygulamalarında şifresiz giriş kaçınılmaz hale geldi. Passkey’ler, WebAuthn ve FIDO2 standartlarıyla desteklenen, biyometri veya cihaz kilidiyle korunan anahtar çiftlerine dayanır. Kullanıcı deneyimini hızlandırır, kimlik avına dayanıklıdır ve iCloud Anahtar Zinciri ile Google Password Manager gibi yöneticiler arasında senkronize olabilir. Bu yazıda, Next.js ve SimpleWebAuthn kütüphanesi ile uçtan uca bir passkey (WebAuthn) entegrasyonunu nasıl kuracağınızı adım adım anlatıyorum.

Neden Passkey?

Passkey, kullanıcının cihazında saklanan bir özel anahtar ile sunucunun sakladığı açık anahtarın eşleşmesine dayanır. Parola gönderilmez, dolayısıyla veri tabanı sızıntılarında parolaların çalınması gibi riskler ortadan kalkar. Kullanıcı cihazında biyometri (Face ID, Touch ID), donanım anahtarı (YubiKey) veya cihaz PIN’i ile doğrulama yapar. Tarayıcı ve platform desteği artık yaygınlaştı; Chrome, Safari ve Firefox, iOS/Android ve masaüstü işletim sistemlerinde kullanılabiliyor.

Mimari ve Gereksinimler

WebAuthn iki ana akış içerir: kayıt (registration) ve kimlik doğrulama (authentication). Her akışta sunucu benzersiz bir challenge üretir, istemci tarafında tarayıcı navigator.credentials API’si ile doğrulama cihazıyla imza atılır ve bu yanıt sunucuda doğrulanır. Üretimde HTTPS zorunludur ve RP ID (Relying Party ID) alan adınızla birebir eşleşmelidir (ör. rpID = example.com).

Teknoloji Seçimi

Örnek kurulum için Next.js 14 (Route Handlers veya App Router), sunucu tarafında @simplewebauthn/server, istemci tarafında @simplewebauthn/browser kullanılabilir. Veritabanı olarak Postgres veya bir KV deposu tercih edebilirsiniz. Saklanacak başlıca alanlar: kullanıcı kimliği, credentialID, publicKey, counter ve tercihen cihaz/metaveri.

Kayıt Akışı (Registration)

1) Kullanıcı e-posta/ID ile kayıt başlatır. Sunucu generateRegistrationOptions ile seçenekleri üretir, challenge’ı geçici olarak saklar ve istemciye döner. Örnek:

const opts = generateRegistrationOptions({ rpName: 'Uygulama Adı', rpID: 'example.com', userName: 'ali', userID: 'user-123', attestationType: 'none', authenticatorSelection: { residentKey: 'preferred', userVerification: 'preferred', authenticatorAttachment: 'platform' } });

2) İstemci, tarayıcıda @simplewebauthn/browser yardımıyla kullanıcıdan biyometri izni ister ve kimlik bilgisi oluşturur:

const attResp = await startRegistration(opts);

3) Sunucu, verifyRegistrationResponse ile gelen cevabı doğrular; doğrulama başarılıysa credentialID, publicKey ve counter değerlerini kullanıcıyla ilişkilendirerek kalıcı olarak saklar.

Giriş Akışı (Authentication)

1) Kullanıcı giriş sayfasında e-posta/ID girer veya koşullu UI ile otomatik olarak öneri alır. Sunucu generateAuthenticationOptions ile yeni bir challenge üretir. İsteğe bağlı olarak yalnızca ilgili kullanıcının kayıtlı cihazlarını allowCredentials ile sınırlandırabilirsiniz.

2) İstemci tarafında çağrı yapılır:

const authResp = await startAuthentication(options);

3) Sunucu verifyAuthenticationResponse ile imzayı ve origin/rpID alanlarını doğrular, counter değerini günceller. Başarılıysa oturumu (cookie/JWT) kurar.

Koşullu UI (Conditional Mediation) ile Tek Tık Giriş

Chrome ve destekleyen tarayıcılarda, kullanıcı adı alanı odaktayken passkey önerilerini otomatik gösterebilirsiniz. Basitçe bir email input’unuz varken:

navigator.credentials.get({ publicKey: authOptions, mediation: 'conditional' });

Bunun çalışması için sayfanız HTTPS olmalı, autocomplete öznitelikleri doğru ayarlanmalı ve kullanıcı daha önce passkey kaydetmiş olmalıdır. Bu yöntem giriş sürtünmesini ciddi şekilde azaltır.

Güvenlik İpuçları ve En İyi Uygulamalar

- RP ID alan adınızla aynı olmalı; yerelde test ederken localhost kullanın veya geçerli bir sertifika ile alt alan adı hazırlayın.

- attestationType: 'none' çoğu senaryo için en iyisidir; gereksiz attestation verisi toplamayın.

- userVerification için 'required' yüksek güvenlikli sayfalar için uygundur (örn. ödeme, ayarlar). Genel girişte 'preferred' iyi bir dengedir.

- Kullanıcıların cihaz değiştirme/ekleme senaryoları için birden fazla passkey kaydına izin verin ve kurtarma seçenekleri (e-posta bağlantısı veya destek akışı) sunun.

- Rate limit, yeniden oynatma (replay) engelleme, kaynak (origin) ve challenge ömrünü doğrulamayı ihmal etmeyin.

Hata Ayıklama ve Test

- SecurityError: The operation is insecure genelde HTTPS veya RP ID uyuşmazlığını gösterir.

- NotAllowedError kullanıcı etkileşimi yokken veya işlemi iptal ettiğinde görülür; buton tıklamasıyla tetikleyin.

- Unknown authenticator veya eşleşmeyen credentialID için doğru kullanıcıyla ilişkilendirme yapıldığından emin olun.

- Tarayıcı konsolu ve about://webauthn (Chrome) test araçları ile sanal güvenlik anahtarı oluşturup akışları yerelde deneyebilirsiniz.

Performans ve UX

Passkey akışı minimal JSON veri alışverişine dayanır; SSR ve edge işleme ile gecikmeyi azaltabilirsiniz. Başarılı kayıt sonrası kullanıcının cihazı üzerinde parolayı da “kaldırmayı” önermek, geçişi hızlandırır. Kullanıcıya hangi cihazların kayıtlı olduğunu gösteren bir yönetim ekranı sunmak güveni artırır.

Sonuç

Passkey (WebAuthn) ile şifresiz giriş, güvenliği artırırken kullanıcı deneyimini de basitleştirir. Next.js ve SimpleWebAuthn ile kurulumu birkaç uç noktaya indirgenebilir: kayıt için seçenek üretme/doğrulama ve giriş için seçenek üretme/doğrulama. RP ID, HTTPS ve challenge yönetimi gibi kritik ayrıntılara dikkat ettiğiniz sürece, modern tarayıcılarda hızlı ve güvenli bir oturum altyapısı sağlayabilirsiniz.

29 Kasım 2025 Cumartesi

pgvector ile RAG Uygulaması: Docker Compose ile PostgreSQL 16 ve Basit Semantik Arama API’si

RAG (Retrieval-Augmented Generation), büyük dil modellerinin (LLM) güncel ve alanınıza özgü verilerle daha doğru yanıtlar üretmesini sağlar. Bu yaklaşımda, soruyu bir vektöre dönüştürür, vektör veritabanından benzer içerikleri geri çağırır ve modeli bu bağlamla beslersiniz. Özel bir vektör veritabanına ihtiyaç duymadan, PostgreSQL 16 + pgvector ile etkili bir semantik arama katmanı kurmak mümkündür. Bu yazıda, Docker Compose ile PostgreSQL’i ayağa kaldıracak, pgvector eklentisini etkinleştirecek ve basit bir API üzerinden semantik arama yapabileceğiz.

Neden PostgreSQL + pgvector? PostgreSQL hâlihazırda üretim ortamlarında yaygın, güvenilir ve güçlü bir ilişkisel veritabanıdır. pgvector eklentisi, vektör tipini, benzerlik metriklerini (cosine, L2, inner product) ve ivfflat/HNSW benzeri indeksleme stratejilerini destekler. Küçük ve orta ölçekli RAG projeleri için tek bir veritabanı üzerinde hem ilişkisel hem de vektörel sorgular yürütmek operasyonel anlamda sade ve maliyet-etkindir.

Önkoşullar: Makinenizde Docker ve Docker Compose kurulu olmalı. Embedding üretimi için OpenAI hesabı (veya yerelde Ollama gibi bir çözüm) işinizi kolaylaştırır. Örneklerde OpenAI’nin “text-embedding-3-small” (1536 boyut) modelini referans vereceğiz; yerel seçenek kullanacaksanız embedding boyutunu ona göre ayarlamalısınız.

Adım 1: Docker Compose ile PostgreSQL 16 ve pgvector
Basit bir docker-compose.yml dosyası oluşturun ve şu servisi tanımlayın:
version: "3.9"
services:
  db:
    image: pgvector/pgvector:pg16
    environment:
      - POSTGRES_USER=rag
      - POSTGRES_PASSWORD=ragpass
      - POSTGRES_DB=ragdb
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data
volumes:
  pgdata:
Dosyayı kaydedip docker compose up -d komutunu çalıştırın. Bu imaj, pgvector eklentisi ile birlikte gelir.

Adım 2: pgvector’ü etkinleştirmek ve şema hazırlığı
Veritabanına bağlanın: psql -h localhost -U rag -d ragdb ve şu komutları çalıştırın:
CREATE EXTENSION IF NOT EXISTS vector;
OpenAI’nin “text-embedding-3-small” boyutuna uygun bir tablo oluşturun:
CREATE TABLE docs (
  id BIGSERIAL PRIMARY KEY,
  content TEXT NOT NULL,
  embedding vector(1536)
);
Arama performansı için ivfflat indeksini kurun (cosine metrik popüler bir tercih):
CREATE INDEX ON docs USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
İndeksin etkili çalışması için tabloyu analiz edin: ANALYZE docs;

Adım 3: Embedding üretimi ve veri ekleme
Örnek içerikler ekleyelim: ürün açıklamaları, SSS, teknik notlar vs. Python ile kısa bir betik yazabilirsiniz. OpenAI için mantık şu şekildedir:
- Metni al, client.embeddings.create(model="text-embedding-3-small", input=metin) ile vektörü üret.
- Vektörü PostgreSQL’e parametreli bir sorgu ile yaz: INSERT INTO docs(content, embedding) VALUES ($1, $2).
Yerel alternatif isterseniz, Ollama’da ollama pull nomic-embed-text diyerek bir embedding modeli çekebilir, http://localhost:11434/api/embeddings üzerinden benzer bir akış kurabilirsiniz. Dikkat: embedding boyutunu (ör. 768) tablo şemanızla tutarlı yapın.

Adım 4: Semantik arama sorgusu
Kullanıcı sorgusunu da embedding’e çevirin ve ORDER BY embedding <=> $1 ifadesiyle benzerliğe göre sıralayın. Örnek bir SQL:
WITH q AS (SELECT $1::vector AS v)
SELECT id, content
FROM docs, q
ORDER BY docs.embedding <=> q.v
LIMIT 5;
Burada <=>, pgvector’ün benzerlik operatörüdür (cosine için mesafe). Bu sonuçları LLM’e bağlam olarak verip RAG yanıtı oluşturabilirsiniz.

Adım 5: Basit bir API ile uçtan uca akış
Hızlı bir prototip için Python FastAPI idealdir. Akış şöyledir:
1) POST /search: query alır, embedding üretir, PostgreSQL’den top-k döner.
2) POST /ask: query ve top-k bağlamı alır, seçtiğiniz LLM (OpenAI veya yerel) ile yanıt üretir.
Üretimde, sonuçları önbelleğe almak (ör. Redis), indeks lists değerini veri büyüklüğüne göre artırmak ve maintenance_work_mem, shared_buffers gibi PostgreSQL ayarlarını optimize etmek önemlidir.

İleri seviye ipuçları
- HNSW: pgvector 0.7+ sürümlerinde HNSW desteği bulunur; okuma gecikmesini düşürür, ancak indexleme süresi ve bellek kullanımı artar. Benchmark yapmadan geçiş yapmayın.
- Normalize embedding: Cosine benzerliğinde vektörleri normalize etmek sonuç tutarlılığını artırabilir.
- Chunking ve metadata: Belgeleri 300–800 token aralığında parçalara bölün. Kaynak, tarih, başlık gibi metadata alanlarını tabloya ekleyin ve sonuçlarda gösterin.
- Güncelleme stratejisi: Sık değişen veriler için upsert akışı oluşturun; embedding’i yalnızca içerik değiştiğinde güncelleyin.
- Güvenlik: Üretimde veritabanını dışarıya kapatın, SSL kullanın, sıkı rol ve politika tanımları yapın.

Sonuç
PostgreSQL 16 ve pgvector ile, ek bir servis karmaşıklığına girmeden güçlü bir semantik arama ve RAG katmanı kurabilirsiniz. Docker Compose yapılandırması hızlı kurulum sağlar; embedding üretimi için OpenAI veya yerel modelleri kullanabilirsiniz. Üzerine basit bir API ekleyerek hem prototip hem de üretim öncesi pilot projelerinizi hızla hayata geçirmeniz mümkün. Doğru indeks, boyut ve ayarlarla, pgvector küçük-orta ölçekli prodüksiyon yüklerini rahatlıkla karşılayacaktır.

28 Kasım 2025 Cuma

RAG ile Türkçe Soru-Cevap Sistemi Kurma: Vektör Veritabanı, Yeniden Sıralama ve Prompt Tasarımı

RAG (Retrieval-Augmented Generation), büyük dil modellerinin (LLM) güncel ve alanınıza özel bilgilerle desteklenmesini sağlayan, üretken yapay zekâ projelerinde giderek standarda dönüşen bir yaklaşımdır. Bu yazıda, Türkçe odaklı bir doküman arama ve soru-cevap sistemini RAG mimarisiyle nasıl kurabileceğinizi; veri hazırlığından vektör veritabanına, yeniden sıralamadan (re-ranking) prompt tasarımına kadar adım adım anlatıyorum.

Amaç, kullanıcı sorusuna dayanarak şirket içi PDF’ler, politika metinleri, ürün katalogları veya destek dökümanları içinden ilgili pasajları bulmak ve LLM’nin yalnızca bu pasajları referans alarak doğru, denetlenebilir cevaplar üretmesini sağlamaktır. Böylece hem halüsinasyonları azaltır hem de cevaplara kaynak gösterebilirsiniz.

Mimari Özeti: RAG iki parçadan oluşur. Birincisi bilgi erişimi (retrieval): Belgeleri parçalayıp (chunking), vektörlerine dönüştürerek bir vektör veritabanına koyar ve sorguya en yakın aday pasajları getirir. İkincisi jenerasyon (generation): LLM, gelen pasajları bağlam olarak kullanıp nihai cevabı üretir. İsteğe bağlı üçüncü bir katman olan yeniden sıralama (re-ranking) ile en iyi adayları üst sıralara taşıyarak doğruluğu yükseltirsiniz.

1) Veri Toplama ve Temizleme: PDF, HTML, Word veya düz metin kaynaklarınızı tek bir havuzda toplayın. Başlık, bölüm, tarih ve izin seviyesi gibi meta verileri koruyun. OCR gerekiyorsa hatalı karakterleri düzeltin, tabloları metne uygun biçimde dönüştürün. Aynı belgenin eski sürümlerini işaretleyerek sürüm çakışmalarını engelleyin.

2) Parçalama (Chunking): Türkçe dilinde bağlamı korumak için paragraf temelli veya cümle temelli parçalama tercih edin. 400–800 token aralığı pratikte iyi sonuç verir; çok küçük parçalarda bağlam kaybolur, aşırı büyük parçalarda ise arama isabeti düşer. Kayma penceresi (overlap) 50–100 token aralığında seçildiğinde cümle bütünlüğü ve referanslar daha sağlam kalır.

3) Gömme (Embeddings) Seçimi: Çok dilli Sentence Transformers tabanlı modeller veya Türkçe uyumlu modern embedding modelleri tercih edin. Türkçe morfolojisi nedeniyle kök/ek varyasyonlarını iyi yakalayan çok dilli modeller pratikte güçlüdür. Vektörleri L2 normla normalize etmeniz kozinüs benzerliği için istikrar sağlar. Boyut (dimensionality) arttıkça isabet artsa da depolama ve gecikme maliyeti yükselir; üretim için 384–1024 aralığı dengelidir.

4) İndeksleme ve Vektör Veritabanı: FAISS ile lokal hızlı prototipleme yapabilir, üretimde Qdrant, Weaviate, Pinecone ya da Elasticsearch’ün yoğun vektör (dense) alanlarını kullanabilirsiniz. HNSW veya IVF-PQ gibi yaklaşık en yakın komşu (ANN) indeksleri gecikmeyi düşürür. Meta veri filtreleme (ör. dil, departman, tarih) ile hassas eşleşme yaparak alakasız sonuçları erkenden eleyin.

5) Hibrit Arama: Tek başına vektör benzerliği bazen anahtar kelime ağırlıklı soruları ıskalayabilir. BM25 + vektör aramayı birleştirerek iki dünyanın en iyisini alabilirsiniz. RRF (Reciprocal Rank Fusion) veya ağırlıklı skor birleştirme ile son aday listesini oluşturun. MMR (Maximal Marginal Relevance) kullanarak çeşitliliği artırın; böylece benzer pasajlar yerine farklı açılardan destekleyen pasajlar üst sıralara gelir.

6) Yeniden Sıralama (Re-ranking): İlk 50–200 adayı, küçük ama hassas bir çapraz-enkoder re-ranker modeliyle yeniden puanlayın. Re-ranker, sorgu ile pasaj arasındaki anlam ilişkisini derinlikli değerlendirir ve isabeti gözle görülür şekilde artırır. Özellikle Türkçe sorgularda, re-ranker kullanımı yanlış pozitifleri ciddi biçimde azaltır.

7) Prompt Tasarımı: Sistem talimatına “Sadece sağlanan bağlamdan yararlan, kaynak yoksa ‘yeterli bilgi yok’ de” gibi net kısıtlar ekleyin. Kullanıcı sorusu, seçilen pasajlar ve gerekirse kısa bir özet talimatı tek bir prompt içinde verilebilir. Her pasajın yanında kaynak kimliği (belge adı, sayfa, bölüm) tutarak yanıta otomatik kaynakça ekleyin; bu, güven ve denetlenebilirlik sağlar.

8) Cevap Üretimi ve Biçimlendirme: Uzun cevaplar için madde işaretleri, kısa cevaplar için net tek paragraf formatı tercih edin. Modelin “uydurma” eğilimini azaltmak için bağlam penceresini verimli kullanın; gerekirse soruyu alt-sorulara bölüp her alt-soru için mini retrieval akışı kurgulayın (multi-step RAG). Hassas alanlarda (hukuk, finans, sağlık) kesinlik dilini yumuşatın ve gerekiyorsa uyarı notu ekleyin.

9) Değerlendirme ve İzleme: Recall@k, MRR, nDCG gibi arama metrikleri ile konteks isabetini ölçün. Yanıt kalitesi için bağlam kapsamı, doğruluk ve kaynak tutarlılığına bakın. Üretim ortamında RAG değerlendirme çerçeveleri (ör. otomatik değerlendirme ve örneklem tabanlı insan denetimi) kurarak sürekli iyileştirme döngüsü oluşturun. Yanlış eşleşen pasajları etiketleyip yeniden eğitime dahil ederek sistematik hataları azaltın.

10) Performans, Maliyet ve Bakıma Dair İpuçları: Sorgu tarafında embedding önbelleği, sonuç tarafında yanıt önbelleği (semantic cache) maliyeti düşürür. ANN indeks parametrelerini (efSearch, nprobe vb.) gecikme/hedef isabet dengesine göre ayarlayın. Kenar cihazlarda veya düşük bütçede 4-bit/8-bit quantized LLM’ler iş görür. Büyük kurumsal koleksiyonlarda artımlı indeks güncellemeleri ve arka plan yeniden inşa stratejisi planlayın.

11) Güvenlik ve Uyumluluk: Kurumsal RAG’da erişim kontrolünü indeks katmanında uygulayın; kullanıcının yetkisi olmayan belgeler retrieval aşamasına hiç girmesin. PII maskeleme ve günlükleme (audit) politikalarını belirleyin. Oran sınırlama (rate limiting) ve anomali tespiti ile kötüye kullanımı engelleyin.

Sık Yapılan Hatalar: Çok büyük chunk boyutları kullanmak, yalnızca vektör aramaya güvenmek, re-ranker atlamak, prompt’ta kaynak gösterimi istememek ve üretim izleme metriklerini kurmamak. Ayrıca embed modeli ile arama dili uyumsuz olduğunda isabet hızla düşer; çok dilli kullanımda model seçimini A/B testleriyle doğrulayın.

Sonuç: RAG, Türkçe belgelerle çalışan soru-cevap sistemleri için pratik, ölçeklenebilir ve denetlenebilir bir çözüm sunar. Doğru chunking, iyi seçilmiş embedding, hibrit arama ve re-ranking üçlüsü; üzerine titizlikle kurgulanmış prompt ve değerlendirme yönergeleri ile birleştiğinde, hem doğruluk hem de kullanıcı güveni açısından sınıf atlatır. Küçük bir pilot ile başlayıp metrikler ışığında iteratif optimize ederek kısa sürede üretime hazır, kaynaklı ve güvenilir bir RAG çözümü elde edebilirsiniz.

27 Kasım 2025 Perşembe

Docker Buildx ile Çoklu Mimarili İmaj Üretimi, İmzalama ve SBOM: Uçtan Uca Rehber

Giriş

Apple Silicon (arm64) ve x86_64 (amd64) dünyasının iç içe geçtiği günümüzde, tek bir Docker imajını birden fazla mimari için üretmek artık bir lüks değil, gereklilik. Üstelik iş yalnızca imajı derlemekle bitmiyor; tedarik zinciri güvenliği gereksinimleri sebebiyle imajları imzalamak ve yazılım malzeme listesi (SBOM) üretmek de kritik hale geldi. Bu rehberde, Docker Buildx ile çoklu mimari imaj üretmeyi, cosign ile imzalamayı ve syft ile SBOM oluşturup imaja iliştirmeyi adım adım anlatıyorum.

Önkoşullar

Makinenizde güncel Docker (mümkünse 24+), Buildx eklentisi (Docker Desktop veya docker-buildx plugin), ve emülasyon için QEMU kurulu olmalı. Çapraz derleme için binfmt yardımıyla arm64/amd64 emülasyonu etkinleştirilebilir. Ayrıca imzalama için cosign, SBOM için syft ve opsiyonel zafiyet taraması için grype kurmanız faydalı olacaktır.

Kontrol komutları: docker buildx version, mevcut builder'ları görmek için docker buildx ls. Gerekirse yeni bir builder oluşturun: docker buildx create --name multiarch --driver docker-container --use. Emülasyon için: docker run --privileged --rm tonistiigi/binfmt --install arm64,amd64.

Çoklu Mimarili İmaj Derleme ve Push

Çoklu mimari imaj üretmenin en pratik yolu Buildx ile manifest list oluşturmaktır. Örnek komut: docker buildx build --platform linux/amd64,linux/arm64 -t ghcr.io/kullanici/uygulama:1.0 --push . Bu komut, her mimari için ayrı imaj derleyip registry'e yükler ve üzerinde mimari bilgisi bulunan tek bir etiket altında (manifest list) birleştirir.

İşin sorunsuz ilerlemesi için taban imajınızın da çoklu mimari desteklemesi gerekir. Örneğin alpine, debian ya da popüler dil imajlarının çoğu arm64/amd64 sürümleri sağlar. Derleme aşamasında Go/Node/Rust gibi dillerde hedef mimariyi belirtmek için ilgili araçların bayraklarından yararlanın; örneğin Go için --build-arg ile GOOS=linux, GOARCH=arm64 gibi değişkenler verilebilir.

Derleme süresini kısaltmak ve tekrarlayan işlerden kaçınmak için BuildKit cache’i kullanın: --cache-to type=registry,ref=ghcr.io/kullanici/uygulama:buildcache,mode=max --cache-from type=registry,ref=ghcr.io/kullanici/uygulama:buildcache. Bu, özellikle CI/CD boru hatlarında çok fark yaratır.

Manifest List Doğrulama

Push tamamlandığında manifest içeriğini şöyle inceleyebilirsiniz: docker buildx imagetools inspect ghcr.io/kullanici/uygulama:1.0. Çıktıda linux/amd64 ve linux/arm64 varyantlarını görmelisiniz. Her cihaz, kendi mimarisine uygun katmanı otomatik olarak çekecektir.

Cosign ile İmaj İmzalama ve Doğrulama

Tedarik zinciri güvenliğinde ilk adım imajları imzalamaktır. Cosign iki biçimde çalışabilir: anahtarlı (key-pair) ve keyless (OIDC). Hızlı başlangıç için keyless önerilir. GitHub veya Google hesabınızla kimlik doğrulama yaparak: COSIGN_EXPERIMENTAL=1 cosign sign ghcr.io/kullanici/uygulama:1.0. Bu komut, imza verisini registry üzerinde imaj referansına bağlı şekilde saklar.

Doğrulama için: cosign verify ghcr.io/kullanici/uygulama:1.0. Çıktıda sertifika zinciri ve imzanın geçerliliği görünmelidir. İmzalama politikanızı CI’da zorunlu kılmak, üretim öncesi kabul kriterlerinin önemli bir parçasıdır.

Syft ile SBOM Üretimi ve İliştirme

SBOM (Software Bill of Materials), imajın içindeki bağımlılıkların envanteridir. Syft ile tek satırda oluşturabilirsiniz: syft ghcr.io/kullanici/uygulama:1.0 -o spdx-json > sbom.spdx.json. Bu dosyayı imaja iliştirmek için cosign attest kullanın: cosign attest --predicate sbom.spdx.json --type spdx ghcr.io/kullanici/uygulama:1.0.

SBOM’u doğrulamak veya çekmek için: cosign verify-attestation ghcr.io/kullanici/uygulama:1.0 ve cosign download attestation ghcr.io/kullanici/uygulama:1.0. SBOM, güvenlik taraması (grype, trivy) ve uyumluluk kontrolleri için referans noktasıdır.

CI/CD Entegrasyonu (GitHub Actions Örneği)

GitHub Actions’da setup-buildx ve login adımlarını ekleyip ardından çoklu mimari derlemeyi tetikleyebilirsiniz. Tipik adımlar: registry’e giriş (docker/login-action), Buildx kurulum (docker/setup-buildx-action), cache ayarları, ardından docker/build-push-action ile platform: linux/amd64,linux/arm64 şeklinde build ve push. Sonraki adımlarda sigstore/cosign-installer ile cosign kurulumu, COSIGN_EXPERIMENTAL=1 cosign sign ve syft ile SBOM üretimi yer alabilir. Workflow gizli değişkenleriyle registry token’larını ve imza politikalarını yönetmeyi unutmayın.

İpuçları ve Yaygın Hatalar

- Taban imajı multi-arch değilse derleme bir mimaride başarılı olurken diğerinde başarısız olabilir. Alternatif taban imajları deneyin veya kendi tabanınızı üretin.

- QEMU emülasyonu derlemeyi yavaşlatabilir. Yerel arm64 runner veya native builder node’ları ile süreyi ciddi şekilde düşürebilirsiniz.

- Reproducible build için sabit sürümler ve kilit dosyaları (go.sum, package-lock.json, Cargo.lock) kullanın. Değişken sürümler, SBOM ve zafiyet taraması sonuçlarını dalgalandırır.

- Cache’i registry üzerinde tutmak, farklı runner’lar arasında tekrar derlemeyi azaltır. mode=max ile en agresif önbelleği etkinleştirebilirsiniz.

- İmza ve attestation objeleri için üretim ve test kayıtlarını ayırın. Etiketleme stratejinizde :dev, :staging, :prod gibi net kanallar oluşturun.

Sonuç

Docker Buildx ile çoklu mimari imaj üretmek, hem geliştirici deneyimini iyileştirir hem de farklı donanım platformlarında tek bir etiket üzerinden dağıtım yapmanızı sağlar. Cosign ile imzalama ve Syft ile SBOM üretimi ise tedarik zinciri güvenliğinizin bel kemiğidir. Bu üç adımı CI/CD boru hattınıza entegre ettiğinizde, yalnızca hızlı değil, aynı zamanda doğrulanabilir ve denetlenebilir bir yayın sürecine sahip olursunuz. Bugünden başlayın; manifest list, imza ve SBOM’u varsayılanınız haline getirin.

26 Kasım 2025 Çarşamba

GitHub Actions ile Çok Mimarili (Multi-Arch) Docker İmajı Oluşturma: Adım Adım Rehber

Modern uygulamalar artık tek bir mimariyle sınırlı kalmıyor. Geliştiriciler, yerel ortamda Apple Silicon (ARM64) üzerinde çalışırken üretimde x86_64 (AMD64) tabanlı sunuculara dağıtım yapabiliyor. Bu çeşitlilikte sorunsuz dağıtım için tek etiket altında birden fazla mimariyi kapsayan "çok mimarili (multi-arch)" Docker imajları kritik hale geliyor. Bu rehberde, GitHub Actions kullanarak multi-arch Docker imajlarını otomatik derleme, imzalama ve kayıt (registry) ortamına gönderme sürecini adım adım anlatıyorum.

Hedefimiz: Her push sonrasında, Docker Buildx ve QEMU emülasyonu ile linux/amd64 ve linux/arm64 platformları için imaj üretmek, doğru etiketleri (tag) eklemek, cache kullanarak derleme süresini kısaltmak ve imajı GHCR (GitHub Container Registry) ya da seçtiğiniz herhangi bir registry'ye göndermek.

Neden Çok Mimarili İmaj?

- Kullanıcılarınızın farklı donanımlarda sorunsuz çalışması için tek imaj etiketi yeterli olur. "docker pull" komutunu çalıştıran istemci, platformuna uygun manifesti otomatik çeker.

- CI/CD süreçlerinde tek kaynaklı doğrulama, test ve güvenlik taraması ile operasyon karmaşıklığını azaltırsınız.

- Tekil sürüm yönetimi sayesinde SLAs ve geri dönüş (rollback) süreçleri sadeleşir.

Önkoşullar

- Proje kök dizininde çalışır bir Dockerfile.

- GitHub Actions kullanabileceğiniz bir depo.

- GHCR kullanacaksanız paket yazma izni; varsayılan GITHUB_TOKEN yeterli olur.

- Tercihen: Semver etiketleri (örn. v1.2.3) ve ana dal (main) akışı.

Adım Adım GitHub Actions Workflow

Aşağıdaki örneği .github/workflows/docker.yml olarak kaydedin. Bu akış, push ve manuel tetiklemede devreye girer, QEMU ve Buildx kurar, GHCR'ye giriş yapar, meta bilgileri üretir ve imajı çok mimarili olarak yayınlar.

name: Build & Push Multi-Arch Image

on:
  push:
    branches: [ "main" ]
    paths:
      - "Dockerfile"
      - "src/**"
      - ".github/workflows/docker.yml"
  workflow_dispatch:

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      id-token: write
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Login to GHCR
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=ref,event=branch
            type=semver,pattern={{version}}
            type=sha
          labels: |
            org.opencontainers.image.source=${{ github.repositoryUrl }}

      - name: Build and push (multi-arch)
        id: build
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64,linux/arm64
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
          cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
          provenance: true
          sbom: true

      # İsteğe bağlı: Keyless imaj imzalama (Sigstore Cosign)
      - name: Install Cosign
        uses: sigstore/cosign-installer@v3

      - name: Sign image (keyless)
        run: cosign sign --yes ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
        env:
          COSIGN_EXPERIMENTAL: "1"

Bu akış, Docker Buildx ile manifest listesi üretir; yani tek bir etikete push etseniz bile altında arm64 ve amd64 varyantları bulunur. "provenance: true" ve "sbom: true" seçenekleri tedarik zinciri şeffaflığı için yazılım malzeme listesi ve oluşturulma kanıtı ekler; güvenlik ve uyumluluk süreçlerinde büyük kolaylık sağlar.

Etiketleme Stratejisi ve Sürümleme

metadata-action, branch adına (örn. main), semantik sürüm etiketlerine (örn. v1.4.0) ve commit SHA'sına göre otomatik tag üretir. Üretimde "latest" tag'ini yalnızca yayın (release) akışlarında basmanızı öneririm; aksi halde test etiketleri ile karışabilir. Ayrıca, immutability sağlamak için SHA tabanlı tag'ler geri dönüş (rollback) senaryolarında hayati önem taşır.

Ön Bellekleme (Cache) ile Hız Kazanın

Build cache'i registry üzerinde saklamak, paralel ve ardışık derlemelerde ciddi zaman kazandırır. Dockerfile adımlarını, bağımlılık indirme ve derleme katmanlarını efektif kullanacak şekilde düzenleyin: Sık değişen kod katmanlarını sona, nadir değişen bağımlılık katmanlarını başa koymak cache verimini artırır.

Güvenlik: İmza, SBOM ve Tarama

Cosign ile keyless imzalama, GitHub'ın OIDC kimlik doğrulamasını kullanarak özel anahtar yönetimini basitleştirir. SBOM üretimi ise açık kaynak lisans takibi ve güvenlik açıkları yönetimi için temel veri sağlar. Buna ek olarak, ayrı bir adımda Trivy veya Grype ile imaj taraması yaparak pipeline'ınızı tamamlayabilirsiniz.

Hızlı Test: Pull ve Çalıştırma

Yerel makinenizde mimarinize göre doğru varyantı çekip çalıştırmak için:

# amd64 veya arm64 üzerinde aynı etiketi çekersiniz
docker pull ghcr.io/<kullanici>/<repo>:main
docker run --rm ghcr.io/<kullanici>/<repo>:main

Manifesti doğrulamak isterseniz "docker buildx imagetools inspect ghcr.io/<kullanici>/<repo>:main" komutunu kullanın; listede linux/amd64 ve linux/arm64 göreceksiniz.

Sık Karşılaşılan Hatalar ve Çözümler

- QEMU bulunamadı: setup-qemu-action adımını kaçırmış olabilirsiniz; sırayı kontrol edin.

- Permission denied (GHCR): packages: write izni ve login-action yapılandırmasını gözden geçirin. Özel registry kullanıyorsanız kullanıcı adı/şifre veya token değerlerini secrets altında tanımlayın.

- Cache çalışmıyor: "cache-from" ve "cache-to" referanslarının aynı olduğundan ve imaja erişim izniniz bulunduğundan emin olun. Ayrıca Dockerfile katman sırasını optimize edin.

- Çok büyük imaj boyutu: Multi-stage build, küçük base imajlar (alpine, distroless) ve "--strip" benzeri derleme optimizasyonlarıyla boyutu düşürün.

Sonuç

GitHub Actions ile multi-arch Docker imajları üretmek, hem geliştirme hem de üretim ortamlarında taşınabilirlik ve güvenilirlik sağlar. Buildx, QEMU, otomatik etiketleme, cache, SBOM ve isteğe bağlı imzalama adımlarıyla kurduğunuz bu zincir, modern DevOps pratiklerinin omurgasını oluşturur. Rehberi projelerinize uyarlayıp aşamalı olarak genişletirseniz, edge cihazlardan bulut altyapılarına kadar tek bir imaj politikasıyla yönetilebilir, sürdürülebilir bir dağıtım stratejisi elde edersiniz.

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.

24 Kasım 2025 Pazartesi

WebGPU ile Tarayıcıda Küçük Dil Modeli (LLM) Çalıştırma: Transformers.js ile Adım Adım Rehber

Tarayıcıda çalışan yapay zeka artık bir merak değil, gerçek bir ürün gerekliliği. WebGPU sayesinde GPU hızlandırmalı çıkarım, kullanıcı verisini sunucuya göndermeden, düşük gecikmeyle mümkün hale geldi. Bu rehberde, Transformers.js kullanarak tarayıcı içinde küçük bir dil modelini (LLM) nasıl çalıştırabileceğinizi adım adım anlatıyorum. Kurulumdan performans ayarlamalarına, gerçek dünyada dağıtıma kadar pratik bir yol haritası bulacaksınız.

Neden tarayıcıda LLM? Gizlilik (veri cihazdan çıkmıyor), offline senaryolar, anında yanıt, ölçeklenebilirlik (sunucu maliyetini uç cihazlara dağıtma) ve daha iyi kullanıcı deneyimi. Ayrıca WebGPU, CPU tabanlı WASM’a kıyasla büyük hız artışları sunuyor.

Gereksinimler ve hazırlık

- Güncel bir Chrome/Edge sürümü (WebGPU varsayılan olarak açık). Safari’de güncel sürümlerde kısmi destek mevcut, Firefox’ta ise Nightly/ayrık yapılandırmalar gerekebilir.
- HTTPS üzerinden sunum (yerelde http://localhost kabul edilir).
- GPU destekli bir cihaz (entegre grafikler de iş görür, ancak ayrık GPU belirgin fark yaratır).

Proje iskeleti: Hızlı başlamak için bir ön yüz iskeleti oluşturun. Örneğin Vite kullanıyorsanız: npm create vite@latest webgpu-llm, ardından proje klasörüne geçip npm install ile bağımlılıkları kurun. Ardından @xenova/transformers paketini ekleyin: npm i @xenova/transformers.

Model seçimi ve indirme stratejisi

Tarayıcıda çalıştırılacak modelin boyutu kritik. 0.1B–1B parametre aralığındaki, Transformers.js uyumlu ve WebGPU desteği eklenmiş dönüştürülmüş (ONNX tabanlı) modelleri tercih edin. Hugging Face üzerinde “Transformers.js”, “onnx” ve “text-generation” etiketlerine bakın. Küçük ama iyi ayarlanmış instruction modelleri, sohbet ve kısa metin üretiminde tatmin edici sonuç verir.

İpucu: İlk açılışta model dosyaları indirileceği için yükleme süresini yönetmek gerekir. Uygulamada bir önbellek stratejisi kullanın (Service Worker ile Cache Storage) ve kullanıcıya ilerleme çubuğu gösterin. Dosyaları parça parça (chunk) indirmek ve CDN üzerinden sunmak açılışı hızlandırır.

Transformers.js ile temel akış

- Yükleme: import ile pipeline fonksiyonunu içeri alın.
- Boru hattı: const generator = await pipeline('text-generation', MODEL_ADI, { device: 'webgpu' }). Burada device olarak webgpu vererek GPU hızlandırmayı etkinleştirirsiniz. Uygun olmayan tarayıcılarda otomatik olarak WASM’a düşebilir.
- Çıkarım: await generator('Merhaba, bugün neler öğrenelim?', { max_new_tokens: 64, temperature: 0.7, top_p: 0.9 }). Parametrelerle çıktı kalitesi ve hız arasında denge kurun.

Akıcı deneyim için akış (stream) modu: Token bazlı akış kullanıcıya “yazıyor” hissi verir. UI tarafında bir akış tamponu tutup yeni token geldikçe metni güncelleyin. İlk yanıtın ortaya çıkma süresi (TTFT) kullanıcı memnuniyeti için kilit metriklerden biridir.

Web Worker ile ana iş parçacığını özgür bırakın

GPU çağrıları ve token üretimi zaman zaman ana iş parçacığını meşgul edebilir. Bir Web Worker oluşturup modeli Worker içinde açın. UI’dan gelen prompt’ları postMessage ile Workera gönderin, tokenları da mesaj olarak geri alın. Bu sayede animasyonlar ve girişler takılmaz.

Not: Çok iş parçacıklı WASM’a düşülen senaryolarda SharedArrayBuffer gerekebilir. Bunun için sunucuda Cross-Origin-Opener-Policy: same-origin ve Cross-Origin-Embedder-Policy: require-corp başlıklarını ayarlayarak cross-origin isolation sağlayın.

Performans ve optimizasyon ipuçları

- Model boyutu ve quantization: INT8 veya 4-bit nicemleme bellek kullanımını ve yükleme süresini düşürür. Küçük modellerde kalite düşüşü sınırlıdır, özellikle kısa yanıtlar için.
- max_new_tokens ve repetition_penalty: Gereksiz uzun cevapların önüne geçer, hız kazandırır.
- Top-p/top-k/temperature: Çeşitlilik ve deterministiklik dengesini kurun. Üretimde genellikle temperature 0.6–0.9, top_p 0.8–0.95 iyi başlama noktalarıdır.
- Önbellek (KV cache): Destekleyen modellerde tekrar token üretiminde hesaplama azaltılır.
- Model varlıklarını yakına alın: Bölgesel CDN veya edge cache ile soğuk başlatmayı kısaltın.
- Lazy ve background preload: Uygulama açılır açılmaz arka planda model dosyalarını ısıtın; kullanıcı prompt yazana kadar yükleme tamamlanmış olur.

Donanım ve tarayıcı farklılıkları: iGPU’larda bellek bant genişliği sınırlıdır; daha küçük model + agresif quantization seçin. Mobil tarayıcılarda enerji tüketimi ve termal kısıtlar nedeniyle kısa oturumları hedefleyin. Masaüstü dGPU’larda ise 1B sınıfı modellere kadar makul akış hızları elde edilebilir.

Hata ayıklama ve yayına hazırlık

- WebGPU yok: Özellik algılama yapın ve kullanıcıya “Hızlandırma devre dışı, CPU modunda çalışıyor” mesajı gösterin.
- Büyük dosya indirme hataları: İndirme yönetimi, yeniden deneme ve parça doğrulaması (checksum) ekleyin.
- Hafıza tavanları: Tarayıcı sekmesinin bellek sınırını aşmayın; model boyutunu ve eşzamanlı çıkarımı sınırlayın.
- Gizlilik: Tüm çıkarım yerelde; telemetri topluyorsanız kullanıcıdan açık onay alın.
- UI/UX: Net yükleme durumu, iptal düğmesi, “Yeniden dene” ve token sayacı gerçek kullanıcı deneyimini iyileştirir.

Gerçek dünyada, bir “akıllı arama” veya “özetleyici” aracı yapmak istiyorsanız RAG (Retrieval Augmented Generation) ile tarayıcı içi gömlemeler (embeddings) üretip, küçük bir vektör dizini (ör. IndexedDB üzerinde) tutabilirsiniz. Arama sonucunu prompt’a enjekte ederek küçük bir modeli, büyük bir modelin doğruluğuna yaklaştırmak mümkün olur.

Özetle: WebGPU + Transformers.js, tarayıcıda LLM çalıştırmayı pratik ve üretime uygun hale getiriyor. Doğru model seçimi, iyi bir önbellekleme stratejisi ve Worker tabanlı mimari ile ilk yanıt süresini düşürür, akıcı bir sohbet deneyimi sunarsınız. Küçük başlayın, kullanıcı geri bildirimlerini toplayın ve gerektiğinde model boyutunu artırın. Bugün tarayıcıda çalışan bir yapay zeka prototipi çıkarmak, artık saatler meselesi.

23 Kasım 2025 Pazar

Tarayıcıda WebGPU ile Yerel LLM Çalıştırma: WebLLM Kurulum Rehberi ve Performans İpuçları

Giriş

Tarayıcıda WebGPU desteğinin olgunlaşması, büyük dil modellerini (LLM) tamamen istemci tarafında çalıştırmayı pratik hale getirdi. Bu rehberde, WebLLM kullanarak Chrome/Edge gibi modern tarayıcılarda internet bağlantısı olmadan çalışan, gizliliğe duyarlı ve hızlı bir sohbet asistanını adım adım kuracağız. Amaç, hem geliştirici deneyimini sade tutmak hem de performans, gizlilik ve SEO açısından en iyi uygulamaları özetlemek.

Ön Gereksinimler

- Güncel bir tarayıcı: Chrome 113+ veya Edge 113+ önerilir. Firefox’ta WebGPU Nightly ile etkinleştirilebilir; macOS’te Safari 17+ destek sunar.

- Güncel ekran kartı sürücüleri: Windows’ta D3D12, Linux’ta Vulkan, macOS’te Metal backend ile WebGPU çalışır. Eski sürücüler “uygun GPU adaptörü bulunamadı” hatasına yol açabilir.

- Node.js 18+ (Vite veya benzeri bir bundler kullanacağız). Alternatif olarak CDN ile basit bir HTML sayfası da kullanılabilir.

Neden WebLLM?

WebLLM, MLC ekosisteminin tarayıcıya optimize edilmiş LLM çalışma zamanı. Avantajları:

- Gizlilik: Veriler cihazı terk etmez. Kurumsal ve kişisel gizlilik gerektiren iş akışları için idealdir.

- Kurulum Kolaylığı: Sunucu yok; CDN veya statik hosting ile dağıtım yapılabilir.

- Performans: 4-bit gibi quantization seçenekleriyle orta segment GPU’larda dahi akıcı deneyim sağlanır.

Hızlı Başlangıç: Vite + WebLLM

Aşağıdaki adımlar React örneğiyle gösterilmiştir; vanilla veya başka framework’ler de benzer şekilde ilerler.

1) Proje oluşturma:

npm create vite@latest webllm-demo -- --template react-ts
cd webllm-demo
npm install

2) Paketleri ekleme:

npm i @mlc-ai/web-llm

3) Basit bir sohbet bileşeni oluşturma (src/App.tsx):

import { useEffect, useState } from "react";
import { CreateMLCEngine, InitProgressReport } from "@mlc-ai/web-llm";

export default function App() {
  const [engine, setEngine] = useState<any>(null);
  const [loading, setLoading] = useState("Model hazırlanıyor...");
  const [input, setInput] = useState("");
  const [messages, setMessages] = useState([{ role: "system", content: "Yardımcı asistan." }]);

  useEffect(() => {
    const init = async () => {
      const model = "Llama-3-8B-Instruct-q4f32_1-MLC"; // Donanımınıza göre daha küçük bir model seçebilirsiniz.
      const eng = await CreateMLCEngine(model, {
        initProgressCallback: (p: InitProgressReport) => {
          setLoading(`${Math.round(p.progress * 100)}% - ${p.text}`);
        },
      });
      setEngine(eng);
      setLoading("");
    };
    init();
  }, []);

  const send = async () => {
    if (!engine || !input.trim()) return;
    const next = [...messages, { role: "user", content: input }];
    setMessages(next);
    setInput("");
    const out = await engine.chat.completions.create({
      messages: next,
      temperature: 0.7,
      max_tokens: 256,
    });
    const reply = out.choices[0].message.content;
    setMessages([...next, { role: "assistant", content: reply }]);
  };

  return (
    <div style={{ padding: 16 }}>
      <h3>WebGPU + WebLLM Yerel Sohbet</h3>
      {loading && <p>{loading}</p>}
      {!loading && (
        <>
          <div style={{ whiteSpace: "pre-wrap", marginBottom: 12 }}>
            {messages.map((m, i) => (
              <div key={i}><b>{m.role}:</b> {m.content}</div>
            ))}
          </div>
          <input
            value={input}
            onChange={(e) => setInput(e.target.value)}
            placeholder="Bir soru yazın..."
            style={{ width: "70%" }}
          />
          <button onClick={send} style={{ marginLeft: 8 }}>Gönder</button>
        </>
      )}
    </div>
  );
}

4) Geliştirme sunucusunu çalıştırma:

npm run dev

İlk başlatmada model dosyaları indirileceği için yükleme biraz sürebilir. Tarayıcı konsolunda ilerleme yüzdesi ve ayrıntıları görürsünüz.

Model Seçimi ve Bellek

Model adı genelde “-MLC” soneki ve quantization içeren bir desen izler: örn. q4f32_1 gibi. 4-bit seçenekler performans/bellek dengesini iyi kurar. 7–8B parametreli Llama 3 veya Phi türevleri, modern entegre GPU’larda dahi çalışabilir. Yüksek VRAM’e sahipseniz daha büyük varyantlar deneyebilirsiniz.

Performans İpuçları

- WebGPU f16: Çoğu cihazda varsayılan olarak etkinleşir; mümkünse f16 yolunu kullanan modelleri tercih edin.

- Maksimum token: Yanıt başına max_tokens değerini gereksiz büyütmeyin; daha kısa yanıtlar daha hızlı üretilecektir.

- İlk yükleme önbelleği: Statik sunucuda HTTP cache ve Service Worker kullanarak model dosyalarını yerel önbelleğe alın; tekrar ziyaretlerde soğuk başlangıç azalır.

- İçerik güvenliği: Üretim ortamında COOP/COEP başlıkları ve sıkı CSP ile hem performans hem de güvenlikte tutarlılık sağlayın.

Güvenlik ve Gizlilik

Tarayıcıda çalışan bir LLM, sohbet içeriklerini dış servislere göndermediği için hassas verilerle çalışırken büyük avantaj sağlar. Yine de kullanıcı verilerini kalıcı olarak saklamadan önce açık rıza isteyin ve localStorage/IndexedDB kullanımını minimal tutun.

Karşılaşılan Yaygın Hatalar

- “No suitable GPU adapter”: Tarayıcınızı ve GPU sürücülerinizi güncelleyin. Linux’ta Vulkan, Windows’ta D3D12 desteğinin aktif olduğundan emin olun.

- Yavaş ilk yükleme: Modeli daha küçük bir varyantla değiştirin (ör. 4-bit), CDN/önbellek ayarlarınızı kontrol edin.

- Tarayıcı desteği: Kurumsal kısıtlı cihazlarda WebGPU devre dışı olabilir; BT politikalarını gözden geçirin veya alternatif tarayıcı deneyin.

SEO İçin Küçük Dokunuşlar

Başlık, meta açıklama ve alt başlıklarda “WebGPU”, “tarayıcıda LLM”, “WebLLM” gibi anahtar kelimeleri doğal akışta kullanın. İlk paragrafta hedef terimleri geçirip, alt başlıklarda soru bazlı ifadeler (ör. “Nasıl kurulur?”, “Hangi modeller hızlı?”) eklemek arama sonuçlarında tıklanma oranını artırır.

Sonuç

WebGPU, tarayıcıyı salt bir istemci olmaktan çıkarıp güçlü bir yerel AI yürütme katmanına dönüştürüyor. WebLLM ile dakikalar içinde çalışan bir sohbet asistanı kurabilir, kurumsal ortamlarda gizlilik avantajı sağlayabilir ve kullanıcılarınıza düşük gecikmeli deneyimler sunabilirsiniz. Küçük bir modelle başlayıp donanımınıza göre ölçeklenin; doğru quantization ve önbellekleme stratejileriyle fark edilir bir performans elde edeceksiniz.

22 Kasım 2025 Cumartesi

WebAuthn ve Passkey ile Parolasız Kimlik Doğrulama: Adım Adım Uygulama Rehberi

WebAuthn ve Passkey Nedir? Neden Önemli?

Parolalar güvenliğin en zayıf halkası olmaya devam ediyor: sızıntılar, kimlik avı, parola tekrar kullanımı ve sosyal mühendislik riski her zamankinden yüksek. WebAuthn (W3C) ve FIDO2 standardı, kullanıcıların cihazlarını (telefon, güvenlik anahtarı, bilgisayar) bir Passkey olarak kullanmasını sağlayarak parolasız, kimlik avına dayanıklı ve kullanıcı dostu bir kimlik doğrulama sunar. Bu yazıda, modern bir web uygulamasında WebAuthn ve Passkey ile parolasız kimlik doğrulamayı adım adım nasıl kuracağınızı anlatıyorum.

Mimari ve Akışın Özeti

WebAuthn iki temel akışa dayanır: kayıt (registration) ve giriş (authentication). Kayıt sırasında sunucu bir “challenge” üretir, tarayıcı ve cihaz bu challenge’ı cihaza özgü özel anahtarla imzalar ve sunucu bu kaydı güvenli şekilde saklar. Girişte yine challenge üretilir ve kullanıcı cihazıyla imza atar; sunucu saklanan genel anahtar ile imzayı doğrular. Bu süreçte kullanıcı deneyimi Touch ID/Face ID, Windows Hello veya bir güvenlik anahtarı dokunuşu kadar basittir.

Önkoşullar ve Dikkat Edilecekler

- HTTPS zorunludur (localhost hariç).
- RP ID (Relying Party ID) genellikle alan adınızdır (ör. example.com).
- Passkey senkronizasyonu için kullanıcılar Apple/Google/Microsoft hesabına giriş yapmış olabilir; güvenlik anahtarı (roaming authenticator) ile de çalışır.
- Tarayıcı desteği modern tarayıcılarda çok iyi; mobilde de yaygın.

Sunucu Tarafı: Challenge Üretimi ve Doğrulama

Sunucuda iki uç nokta gerekir: kayıt için “options” ve “verify”, giriş için “options” ve “verify”. Node.js dünyasında @simplewebauthn/server popüler ve güvendiğim bir çözümdür. Aşağıda yalın bir örnek görebilirsiniz.

// npm i @simplewebauthn/server express cors
import express from 'express';
import cors from 'cors';
import {
  generateRegistrationOptions,
  verifyRegistrationResponse,
  generateAuthenticationOptions,
  verifyAuthenticationResponse,
} from '@simplewebauthn/server';

const app = express();
app.use(cors());
app.use(express.json());

const rpID = 'example.com';
const rpName = 'Örnek Uygulama';
const db = { users: new Map() }; // { userId: { id, username, credentials: [] } }

app.post('/webauthn/register/options', (req, res) => {
  const { userId, username } = req.body;
  const user = db.users.get(userId) || { id: userId, username, credentials: [] };
  const opts = generateRegistrationOptions({
    rpID,
    rpName,
    userID: user.id,
    userName: user.username,
    attestationType: 'none',
    excludeCredentials: user.credentials.map(c => ({ id: c.credentialID, type: 'public-key' })),
    authenticatorSelection: { residentKey: 'required', userVerification: 'required' },
  });
  user.currentChallenge = opts.challenge;
  db.users.set(userId, user);
  res.json(opts);
});

app.post('/webauthn/register/verify', async (req, res) => {
  const { userId, attResp } = req.body;
  const user = db.users.get(userId);
  const verification = await verifyRegistrationResponse({
    response: attResp,
    expectedChallenge: user.currentChallenge,
    expectedRPID: rpID,
    expectedOrigin: 'https://example.com',
  });
  if (verification.verified) {
    const { credentialPublicKey, credentialID, counter } = verification.registrationInfo;
    user.credentials.push({ credentialID, credentialPublicKey, counter });
    db.users.set(userId, user);
  }
  res.json({ ok: verification.verified });
});

app.post('/webauthn/auth/options', (req, res) => {
  const { userId } = req.body;
  const user = db.users.get(userId);
  const opts = generateAuthenticationOptions({
    rpID,
    userVerification: 'required',
    allowCredentials: user.credentials.map(c => ({ id: c.credentialID, type: 'public-key' })),
  });
  user.currentChallenge = opts.challenge;
  db.users.set(userId, user);
  res.json(opts);
});

app.post('/webauthn/auth/verify', async (req, res) => {
  const { userId, authResp } = req.body;
  const user = db.users.get(userId);
  const verification = await verifyAuthenticationResponse({
    response: authResp,
    expectedChallenge: user.currentChallenge,
    expectedRPID: rpID,
    expectedOrigin: 'https://example.com',
    authenticator: user.credentials.find(c => c.credentialID.equals(Buffer.from(authResp.rawId, 'base64url'))),
  });
  res.json({ ok: verification.verified });
});

app.listen(3000);

Ön Uç: Tarayıcıda Kayıt ve Giriş

Tarayıcı API’si navigator.credentials üzerinden çalışır. Dönüş tipleri ArrayBuffer içerdiğinden Base64URL dönüşümleri gerekir. Basitlik için @github/webauthn-json veya @simplewebauthn/browser kullanabilirsiniz.

// npm i @simplewebauthn/browser
import {
  create as webauthnCreate,
  get as webauthnGet,
} from '@simplewebauthn/browser';

async function registerPasskey(userId, username) {
  const opts = await fetch('/webauthn/register/options', {
    method: 'POST', headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({ userId, username }),
  }).then(r => r.json());

  const attResp = await webauthnCreate({ publicKey: opts });
  const verify = await fetch('/webauthn/register/verify', {
    method: 'POST', headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({ userId, attResp }),
  }).then(r => r.json());

  return verify.ok;
}

async function loginWithPasskey(userId) {
  const opts = await fetch('/webauthn/auth/options', {
    method: 'POST', headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({ userId }),
  }).then(r => r.json());

  const authResp = await webauthnGet({ publicKey: opts });
  const verify = await fetch('/webauthn/auth/verify', {
    method: 'POST', headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({ userId, authResp }),
  }).then(r => r.json());

  return verify.ok;
}

Güvenlik ve UX İpuçları

- Kullanıcı doğrulamasını “required” tutun; cihaz biyometri veya PIN ister.
- Passkey’i birincil giriş yöntemi yapın, parolayı opsiyonel geri dönüş kanalı olarak değerlendirin.
- Aynı kullanıcı için birden fazla autentikatör kaydına izin verin (telefon, laptop, güvenlik anahtarı).
- Cihaz değiştirmede QR ile “cihazlar arası” passkey oturumu deneyimini etkinleştirin (Chrome/Safari destekler).
- Kaydı tamamlamadan önce challenge ve RP ID eşleşmelerini mutlaka doğrulayın.

Hata Ayıklama: Sık Karşılaşılan Sorunlar

- origin ve rpID eşleşmiyor: HTTPS alan adınız ile RP ID aynı olmalı (subdomain farklarına dikkat).
- Base64URL dönüşümleri eksik: webauthn-json veya simplewebauthn/browser kullanın.
- localhost testi: http://localhost için istisna var, ancak 127.0.0.1 farklı kabul edilebilir; tutarlı kullanın.
- allowCredentials boş: Kullanıcının kayıtlı credential listesini girişte doğru döndürdüğünüzden emin olun.

Sonuç

WebAuthn ve Passkey ile parolasız kimlik doğrulama artık niş bir teknoloji değil; büyük tarayıcılar ve platformlarca olgun şekilde destekleniyor. Doğru kütüphanelerle kurulum süreci şaşırtıcı derecede kısa, güvenlik kazanımı ise çok büyük. Ürününüzde parolaları azaltmak ve kimlik avına dayanıklı, modern bir deneyim sunmak istiyorsanız, bugün küçük bir POC ile başlayın ve kullanıcı tabanınıza kademeli olarak yayınlayın.

21 Kasım 2025 Cuma

Passkeys (FIDO2/WebAuthn) ile Şifresiz Kimlik Doğrulama: Adım Adım Uygulama Rehberi

Passkey nedir ve neden şimdi?

Parola yorgunluğu, kimlik avı ve bot saldırıları modern uygulamaların en büyük tehditlerinden. Passkey, FIDO2 ve WebAuthn standartlarına dayanan, parolasız ve kimlik avına dirençli bir doğrulama yaklaşımıdır. Kullanıcı, cihazındaki biyometri (parmak izi, Face ID) veya PIN ile giriş yapar; tarayıcı, cihazda üretilen özel anahtarı kullanır ve sunucuya sadece genel anahtar gider. Böylece veri ihlallerinde çalınacak parola kalmaz.

Mimariyi bir bakışta anlayın

WebAuthn istemci tarafı (tarayıcı ve platform autentikatörü) ile sunucu arasında çalışır. Kayıtta, sunucu bir “challenge” üretir; tarayıcı bu challenge’ı cihazın güvenli çipinde üretilen özel anahtar ile imzalar ve sunucuya genel anahtar + imza döner. Girişte de benzer şekilde geçici bir challenge imzalanır. Sunucu, daha önce sakladığı genel anahtar ile imzayı doğrular ve kullanıcıyı oturum açmış sayar.

Önkoşullar ve planlama

1) Alan adınız için HTTPS zorunludur. 2) Sunucu tarafında WebAuthn doğrulama yapabilecek bir kütüphane seçin (örn. dilinize uygun FIDO2/WebAuthn paketleri). 3) Kullanıcı akışını planlayın: önce passkey kaydı, ardından giriş. 4) Çoklu cihaz senaryosunu düşünün: senkronize passkey (Apple/Google ekosistemleri) ve güvenlik anahtarlarını (YubiKey vb.) desteklemek isteyebilirsiniz.

Kayıt (Registration) akışı

Adım 1: Sunucu, kullanıcı için bir kayıt challenge’ı ve RP (Relying Party) bilgilerini üretir. Adım 2: Tarayıcıda WebAuthn API’sini çağırarak cihazın bir anahtar çifti oluşturmasını istersiniz. Adım 3: Kullanıcı biyometri veya cihaz PIN’i ile onay verir. Adım 4: Tarayıcı, oluşturulan genel anahtarı, attestation ve ilgili meta verileri sunucuya gönderir. Adım 5: Sunucu, gelen veriyi doğrular; geçerli ise kullanıcı hesabına bu genel anahtarı bağlar. Bu aşamadan sonra kullanıcı, parolasız giriş yapabilecek duruma gelir.

Giriş (Authentication) akışı

Adım 1: Sunucu, giriş yapmak isteyen kullanıcı için bir challenge üretir ve tarayıcıya yollar. Adım 2: Tarayıcı, cihazda saklı özel anahtarı kullanarak challenge’ı imzalar; kullanıcı yine biyometri veya PIN ile onay verir. Adım 3: İmzalı veri sunucuya döner. Adım 4: Sunucu, ilgili genel anahtar ile imzayı doğrular ve başarılıysa oturumu başlatır. Tüm süreçte parola veya tek seferlik SMS kodu gerekmez.

Sunucu tarafı doğrulama ipuçları

Doğrulamada RP ID, origin ve challenge eşleşmelerine dikkat edin. Signature counter (sign count) değerini izleyerek olası anahtar klonlama şüphesini tespit edin. Mümkünse attestation doğrulamasını aktif edin ve yalnızca güvenilir üreticilerin autentikatörlerine izin verin. Kullanıcı başına birden çok passkey kaydını desteklemek, hesap kurtarma ve cihaz değişimi için hayat kurtarır.

Kullanıcı deneyimi: Mikro kılavuz

Kullanıcıları “Parolasız giriş” kavramına kısa bir açıklama ile hazırlayın. Kayıtta hangi yöntemlerin desteklendiğini (cihazın dahili biyometrisi, harici güvenlik anahtarı) net ifade edin. Giriş ekranında “Passkey ile devam et” butonu görünür olmalı; alternatif olarak e-posta ile büyülü bağlantı (magic link) veya yedek kodlar ikinci seçenek olarak sunulabilir. Cihaz desteklemiyorsa anlaşılır hata mesajları verin ve geçici olarak SMS/OTP gibi yöntemlere yönlendirin.

Güvenlik en iyi uygulamalar

- HTTPS her ortamda zorunlu. - CSRF ve oturum sabitleme saldırılarına karşı koruma sağlayın. - Rate limiting ve IP itibar kontrolleri ekleyin. - Kademeli yayına geçin: önce beta kullanıcıları ile test, sonra genelleme. - Attestation politikasını, regülasyon gerekliliklerine (ör. finans, sağlık) uyacak şekilde ayarlayın.

Uyumluluk ve tarayıcı desteği

Modern Chromium, Safari ve Firefox tarayıcıları WebAuthn’ı destekliyor. Mobilde iOS ve Android tarafında platform autentikatörleri olgunlaştı. Eski cihazlarda destek kısıtlı olabilir; bu nedenle özelliği feature detection ile koşullu olarak göstermek iyi bir pratik. Ayrıca kurumsal ağlarda güvenlik anahtarlarını tercih eden ekipler için USB/NFC/Bluetooth seçeneklerini dökümante edin.

Performans ve ölçüm

Kayıt ve giriş sürelerini ölçün; özellikle challenge üretimi ve sunucu doğrulama gecikmelerini izleyin. Hata oranlarını, başarısız biyometri denemelerini ve cihaz uyumluluk dağılımını panolara taşıyın. A/B testleri ile “passkey öncelikli” akışların dönüşüm oranlarını kıyaslayın; genellikle ilk oturum açma süreleri ve tekrar girişler belirgin şekilde hızlanır.

Geçiş stratejisi ve hesap kurtarma

Var olan parola tabanlı sisteme passkey eklerken kademeli geçiş önerilir. Önce iki faktör olarak ekleyin, ardından parolasız moda yükseltin. Hesap kurtarma için ikinci e-posta, güvenlik anahtarı veya doğrulanmış cihazda QR onayı gibi seçenekler hazırlayın. Parola sıfırlama sürecini, passkey kayıtlarını koruyacak şekilde yeniden tasarlayın ve her kritik adımda ek onay isteyin.

Sonuç

Passkeys, parolaların zayıflıklarını ortadan kaldıran, kullanıcı dostu ve güvenliği yüksek bir çözüm sunuyor. Doğru akış tasarımı, sıkı sunucu doğrulaması ve temiz bir UX ile WebAuthn’ı uygulamanıza eklemek düşündüğünüzden daha hızlı olabilir. Bugün küçük bir kullanıcı grubunda başlatın, metrikleri izleyin ve parolasız geleceğe emin adımlarla geçin.

20 Kasım 2025 Perşembe

Passkey (WebAuthn) ile Şifresiz Giriş Uygulaması: Adım Adım Geliştirici Rehberi

Giriş

Şifresiz kimlik doğrulama, hem güvenliği artırmak hem de kullanıcı deneyimini iyileştirmek için son yılların en önemli trendlerinden biri. Passkey (WebAuthn/FIDO2) yaklaşımı, parola sızıntılarını ve kimlik avı risklerini azaltırken tek dokunuşla giriş deneyimi sunuyor. Bu yazıda, modern bir web uygulamasına passkey tabanlı şifresiz girişi nasıl entegre edebileceğinizi adım adım ele alacağız. Anlatım sade, SEO uyumlu ve güncel standartları takip edecek şekilde hazırlandı.

Passkey Nedir ve Neden Önemlidir?

Passkey, FIDO2 ve WebAuthn standartlarının bir ürünüdür. Kullanıcının cihazında güvenli bir şekilde saklanan asimetrik anahtar çifti ile çalışır. Sunucu sadece kullanıcının açık anahtarını tutar; özel anahtar cihazdan çıkmaz. Böylece parola sızıntıları, tekrar kullanım ve kimlik avı (phishing) riskleri büyük ölçüde azalır. Ek olarak, platform doğrulayıcıları (iOS/Android/Windows/macOS) ve bulut senkronizasyonu sayesinde kullanıcı deneyimi neredeyse otomatik doldurma kadar kolay hale gelir.

Temel Kavramlar

Relying Party (RP) ID, genellikle uygulamanızın kök etki alanıdır ve WebAuthn işlemlerinin bağlandığı kimliktir. Origin ise protokol, alan adı ve portun birleşimidir; HTTPS zorunludur. Kayıt sırasında attestation, giriş sırasında assertion adı verilen imzalı veriler üretilir. Sunucu bu verileri doğrular, sayaç (signCount) ile tekrar saldırılarını engeller ve açık anahtarı (public key) güvenli şekilde saklar.

Gereksinimler

Üretimde HTTPS zorunludur; geliştirirken localhost istisna olarak desteklenir. Sunucu tarafında bir WebAuthn doğrulama kütüphanesi kullanmanız pratik olacaktır. Veritabanında kullanıcı kimliği ile eşleştirilmiş credentialId, publicKey, signCount, transports ve tercihe göre attestation bilgilerini saklamalısınız. Tarayıcı tarafında WebAuthn API’sini (navigator.credentials) kullanacaksınız.

Adım 1: RP ID ve Origin Doğrulaması

RP ID, example.com gibi kaydedilebilir kök alan adı olmalıdır. Alt alan adlarında çalışan uygulamalar için RP ID’yi kök alan olarak belirlemek, farklı alt alanlarda aynı passkey’i kullanabilmenizi sağlar. Üretimde https://example.com gibi tam bir origin kullanmalı ve yönlendirmelerinizin origin’i değiştirmediğinden emin olmalısınız.

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

Önce sunucu, benzersiz bir challenge üretir ve istemciye publicKeyCredentialCreationOptions ile gönderir. Bu seçeneklerde rp, user, challenge, pubKeyAlg (örn. ES256), authenticatorSelection (residentKey ve userVerification tercihleri) ve timeout gibi değerler bulunur. İstemci, navigator.credentials.create({ publicKey: options }) çağrısı ile passkey oluşturur. Elde edilen attestation yanıtını sunucuya gönderirsiniz. Sunucu tarafında imza doğrulanır, RP ID hash’i ve origin kontrol edilir, ardından credentialId, publicKey ve signCount veritabanına kaydedilir. Kullanıcı deneyimi için kayıt sırasında en az iki farklı cihazda passkey oluşturmayı önermek, hesabın kurtarılabilirliğini artırır.

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

Girişte sunucu yeni bir challenge üretir ve publicKeyCredentialRequestOptions ile istemciye gönderir. Eğer keşfedilebilir kimlik bilgileri (discoverable credentials) kullanıyorsanız kullanıcı adı sormadan doğrudan passkey seçimi mümkün olur. İstemci navigator.credentials.get({ publicKey: options }) çağrısı ile assertion üretir. Sunucu imzayı ve sayaç artışını doğrular; signCount beklenenden düşükse anahtarın çoğaltılmış olabileceğine dair alarm üretir. Başarılı doğrulamadan sonra oturum açılır ve standart oturum yönetimi (cookie veya token) uygulanır.

Adım 4: Conditional UI ve Otomatik Doldurma

Uygun tarayıcılarda “conditional UI” ile kullanıcı adı alanında beklerken passkey önerileri gösterilebilir. Bu deneyim için mediation: "conditional" parametresi ve ilgili tarayıcı desteği gerekir. Kullanıcı adı girmeden tek dokunuşla giriş, dönüşüm oranlarını belirgin biçimde artırabilir.

Güvenlik En İyi Uygulamaları

HTTPS her zaman zorunlu olmalıdır. userVerification’ı mümkünse required yaparak biyometrik/PIN doğrulaması isteyin. COSE algoritmalarını ES256 gibi modern seçeneklerle sınırlayın. Timeout değerlerini gerçekçi tutun ve hataları ayrıntılı ama saldırgana ipucu vermeyecek şekilde loglayın. RP ID ve origin uyuşmazlıkları, SecurityError hatalarının en yaygın sebebidir; alan adı, www/alt alan ayrımları ve proxy ayarlarını dikkatle kontrol edin.

Veri Tabanı Tasarımı

Kullanıcı başına birden çok credential saklayın. Her kayıt için credentialId (ikili/BASE64URL), publicKey (COSE/PEM), signCount (tamsayı), transports (usb, nfc, ble, internal), aaguid ve oluşturulma tarihi alanları tutarlı bir şema oluşturur. Silinen veya kaybolan cihazlar için güvenli kaldırma (soft delete) ve denetim kayıtları (audit log) eklemeniz önerilir.

UX İpuçları

Kullanıcıyı bilgilendiren net mikro metinler kullanın: “Cihaz kilidini açın ve dokunun” gibi. Başarısız denemelerde net ama güvenli hata mesajları verin. İlk kayıt sonrasında “Başka bir cihaz ekle” önerisi sunarak kurtarma olasılığını artırın. Passkey’i mevcut parolalı hesaplara ek ikinci faktör olarak başlatıp zamanla parolayı kaldırma stratejisi de uygulanabilir.

Hata Ayıklama ve Yaygın Sorunlar

NotAllowedError genellikle kullanıcı işlemi iptal ettiğinde veya zaman aşımında görülür. SecurityError RP ID/origin uyumsuzluğunu işaret eder. UnknownError donanım doğrulayıcı veya platform politikası sorunlarına işaret edebilir. Windows Hello için cihazda PIN/Biyometri etkin olmalı; mobilde ekran kilidi şarttır.

Sonuç

Passkey (WebAuthn) ile şifresiz giriş, modern web projelerinde güvenliği artırırken sürtünmeyi azaltır. Doğru RP ID, güvenli sunucu doğrulaması, kullanıcı dostu akış ve çok cihazlı strateji ile hem ölçeklenebilir hem de dayanıklı bir kimlik doğrulama katmanı kurabilirsiniz. Bugünden küçük bir pilotla başlayıp kullanıcı geri bildirimlerine göre geliştirmek, geçiş maliyetini düşürmenin en verimli yoludur.

19 Kasım 2025 Çarşamba

Next.js ile Passkey (WebAuthn/FIDO2) Entegrasyonu: Şifresiz Giriş Nasıl Eklenir?

Özet: Passkey (WebAuthn/FIDO2) ile şifresiz kimlik doğrulama, kimlik avına dayanıklı ve kullanıcı deneyimi yüksek modern bir giriş yöntemidir. Bu rehberde, Next.js üzerinde passkey kaydı ve giriş akışını adım adım nasıl kuracağınızı anlatıyorum. Amaç, pratik bir örnekle üretime yakın bir mimari oluşturmak ve SEO dostu bir içerikle temel kavramları sade şekilde aktarmaktır.

Passkey Nedir ve Neden Önemli?

Passkey, kullanıcıların parolasız şekilde oturum açmasını sağlayan, cihazın güvenli donanım modülü veya güvenli alanında saklanan FIDO2/WebAuthn kimlik bilgilerine dayanan bir yöntemdir. Phishing’e karşı dirençlidir, ikinci faktör ihtiyacını ortadan kaldırabilir ve kullanıcı tarafında dokunma/yüz tanıma gibi biyometrik doğrulamalarla çalışır. iOS, Android, Windows ve macOS’ta platform desteği geniştir.

Gereksinimler

- Next.js 13/14 (App Router veya Pages Router)
- Node.js 18+
- HTTPS (geliştirmede localhost istisnası vardır)
- Sunucu tarafında oturum (session) veya kısa süreli challenge saklama mekanizması
- Bir veritabanı (ör. PostgreSQL, SQLite veya MongoDB)

Mimari ve Terimler

RP ID (Relying Party ID): Genellikle alan adınızın eTLD+1 formatı (ör. example.com). Localhost için rpID: localhost kullanılabilir.
Challenge: Sunucuda üretilen tek seferlik rastgele dize; hem kayıt hem girişte imzalanır ve doğrulanır.
Credential: Kullanıcıya ait public key, credentialId, counter ve transports gibi bilgilerden oluşur.

Kurulum

Next.js projesi oluşturun ve gerekli paketleri ekleyin:

npx create-next-app passkey-app
cd passkey-app
npm i @simplewebauthn/server @simplewebauthn/browser base64url

Veritabanında şu alanlara ihtiyacınız olacak: userId, credentialId (Buffer/Base64URL), publicKey (PEM/COSE), counter (number), transports (string[]). Örnek bir tablo/collection şeması oluşturun.

Kayıt (Registration) Akışı

1) Sunucu tarafında kayıt seçeneklerini üretin ve challenge’ı oturumda saklayın:

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

export async function GET() {
  const rpID = process.env.RP_ID || 'localhost';
  const user = { id: 'u_123', name: '[email protected]', displayName: 'Ali' };

  const options = await generateRegistrationOptions({
    rpName: 'Passkey Demo',
    rpID,
    userID: user.id,
    userName: user.name,
    userDisplayName: user.displayName,
    attestationType: 'none',
    authenticatorSelection: {
      residentKey: 'preferred',
      userVerification: 'required',
      authenticatorAttachment: 'platform',
    },
  });

  // TODO: session.set('currentChallenge', options.challenge)
  return Response.json(options);
}

2) İstemci tarafında kullanıcının cihazında passkey oluşturun:

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

export default function Register() {
  const onRegister = async () => {
    const opts = await fetch('/api/webauthn/register-options').then(r => r.json());
    const attRes = await startRegistration(opts);
    await fetch('/api/webauthn/register-verify', {
      method: 'POST',
      body: JSON.stringify(attRes),
    });
  };
  return <button onClick={onRegister}>Passkey Oluştur</button>;
}

3) Sunucu tarafında yanıtı doğrulayın ve kimlik bilgilerini veritabanına kaydedin:

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

export async function POST(req: Request) {
  const body = await req.json();
  const expectedChallenge = /* session.get('currentChallenge') */ '';
  const rpID = process.env.RP_ID || 'localhost';
  const origin = process.env.ORIGIN || 'http://localhost:3000';

  const { verified, registrationInfo } = await verifyRegistrationResponse({
    expectedRPID: rpID,
    expectedOrigin: origin,
    expectedChallenge,
    response: body,
  });

  if (verified && registrationInfo) {
    const { credentialPublicKey, credentialID, counter, credentialDeviceType, credentialBackedUp } = registrationInfo;
    // TODO: DB'ye userId ile birlikte credentialID, credentialPublicKey, counter, transports kaydedin
  }
  return Response.json({ verified });
}

Giriş (Authentication) Akışı

1) Sunucuda kullanıcıya ait credentialId listesini kullanarak seçenek üretin:

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

export async function GET() {
  const rpID = process.env.RP_ID || 'localhost';
  const userCreds = [ /* DB'den credentialId'ler */ ];

  const options = await generateAuthenticationOptions({
    rpID,
    userVerification: 'required',
    allowCredentials: userCreds.map(c => ({
      id: c.credentialId,
      type: 'public-key',
      transports: c.transports || ['internal'],
    })),
  });

  // TODO: session.set('currentChallenge', options.challenge)
  return Response.json(options);
}

2) İstemci tarafında kimlik doğrulaması başlatın ve yanıtı sunucuya gönderin:

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

export default function Login() {
  const onLogin = async () => {
    const opts = await fetch('/api/webauthn/auth-options').then(r => r.json());
    const assertion = await startAuthentication(opts);
    const res = await fetch('/api/webauthn/auth-verify', {
      method: 'POST',
      body: JSON.stringify(assertion),
    });
    const { verified } = await res.json();
    if (verified) alert('Giriş başarılı');
  };
  return <button onClick={onLogin}>Passkey ile Giriş</button>;
}

3) Sunucuda doğrulama yapın ve counter değerini güncelleyin:

// app/api/webauthn/auth-verify/route.ts
import { verifyAuthenticationResponse } from '@simplewebauthn/server';

export async function POST(req: Request) {
  const body = await req.json();
  const expectedChallenge = /* session.get('currentChallenge') */ '';
  const rpID = process.env.RP_ID || 'localhost';
  const origin = process.env.ORIGIN || 'http://localhost:3000';

  // TODO: DB'den credentialId'ye göre publicKey ve counter'ı çekin
  const dbCred = {/* ... */};

  const { verified, authenticationInfo } = await verifyAuthenticationResponse({
    expectedRPID: rpID,
    expectedOrigin: origin,
    expectedChallenge,
    response: body,
    authenticator: {
      credentialID: dbCred.credentialId,
      credentialPublicKey: dbCred.publicKey,
      counter: dbCred.counter,
      transports: dbCred.transports,
    },
  });

  if (verified) {
    // TODO: DB'de counter'ı authenticationInfo.newCounter ile güncelleyin
    // TODO: uygulama oturumunu başlatın
  }
  return Response.json({ verified });
}

Koşullu UI ve Kullanılabilirlik İpuçları

- Chrome’da “conditional UI” ile giriş formu göstermeden tarayıcı otomatik passkey önerisi sunabilir. startAuthentication çağrısını buna göre optimize edin.
- Kullanıcıya “Aygıtınızda passkey saklanacak” gibi kısa, anlaşılır mesajlarla rehberlik edin.
- Eşitleme: iCloud Keychain veya Google Password Manager ile passkey’ler cihazlar arasında senkronize olabilir.

Güvenlik ve Üretim Notları

- Her zaman HTTPS kullanın; origin ve rpID tutarlılığı hayati önem taşır.
- Challenge değerlerini kısa ömürlü ve tek kullanımlık tutun.
- Çoklu cihaz senaryolarında bir kullanıcıya birden fazla credential kaydına izin verin.
- Hata Yönetimi: NotAllowedError genelde kullanıcı iptali veya zaman aşımından kaynaklanır; kullanıcı dostu hata mesajları gösterin.

Sonuç olarak, Next.js üzerinde passkey entegrasyonu birkaç API uç noktası ve doğru challenge yönetimiyle oldukça akıcı bir hale geliyor. Parolaları terk etmek hem güvenliği artırır hem de dönüşüm oranlarını yükseltir. Üretime alırken oturum süreleri, cihaz yönetimi ve kurtarma seçeneklerini (ör. e-posta magic link) birlikte kurgulamanızı öneririm.

18 Kasım 2025 Salı

Docker Compose ile Traefik v3, Cloudflare ve Let's Encrypt ile Otomatik Reverse Proxy Kurulumu

Giriş

Kendi sunucunuzda birden fazla web uygulamasını alan adlarıyla yönetmek, güvenli SSL sertifikalarını otomatik yenilemek ve trafik yönlendirmelerini zahmetsizce yapmak istiyorsanız, Traefik v3 sizin için biçilmiş kaftan. Bu yazıda, Docker Compose kullanarak Traefik v3 ile Cloudflare DNS ve Let’s Encrypt entegrasyonunu adım adım kuracağız. Hedefimiz: tek bir reverse proxy arkasında otomatik HTTPS, kolay yönlendirme ve minimum bakım maliyeti.

Neden Traefik v3?

Traefik, container tabanlı ortamlarda dinamik servis keşfi, otomatik TLS ve güçlü yönlendirme kurallarıyla öne çıkıyor. v3 sürümü, performans ve güvenlik iyileştirmeleri ile birlikte yapılandırma tutarlılığı sunuyor. Docker üzerinde çalışan mikro servisleriniz için exposedByDefault=false gibi güvenli varsayılanlarla ince ayar yapmak da oldukça kolay.

Önkoşullar

- Docker ve Docker Compose kurulu bir Linux sunucu (ör. Ubuntu 22.04).

- 80 ve 443 portlarının dış dünyaya açık olması.

- Alan adınız Cloudflare üzerinde yönetiliyor olmalı.

- Cloudflare üzerinde DNS düzenleme yetkisine sahip bir API Token (Zone.DNS.Edit, Zone.Read).

- Let’s Encrypt için bir e-posta adresi.

Adım 1: Cloudflare API Token oluşturun

Cloudflare hesabınızda User Profile > API Tokens > Create Token yolunu izleyin. “Edit zone DNS” şablonunu seçip alan adınızı kısıtlayın. Token’ı güvenle saklayın; birazdan docker-compose içinde kullanacağız.

Adım 2: Proje dizinini hazırlayın

Sunucuda boş bir klasör oluşturup içine docker-compose.yml ve isteğe bağlı .env dosyası yerleştireceğiz. Aşağıdaki örnek, Traefik v3’ü, Cloudflare DNS-01 challenge ile Let’s Encrypt sertifika yenilemesi yapacak şekilde hazırlar.

version: "3.8"

services:
  traefik:
    image: traefik:v3.0
    container_name: traefik
    command:
      - --api.dashboard=true
      - --providers.docker=true
      - --providers.docker.exposedByDefault=false
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443
      - [email protected]
      - --certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.letsencrypt.acme.dnschallenge=true
      - --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare
    ports:
      - "80:80"
      - "443:443"
    environment:
      - CF_DNS_API_TOKEN=${CF_DNS_API_TOKEN}
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.http-catchall.rule=HostRegexp(`{host:.+}`)"
      - "traefik.http.routers.http-catchall.entrypoints=web"
      - "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
      - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
      - "traefik.http.routers.traefik.rule=Host(`proxy.example.com`)"
      - "traefik.http.routers.traefik.entrypoints=websecure"
      - "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
      - "traefik.http.routers.traefik.service=api@internal"
      - "traefik.http.middlewares.dashboard-auth.basicauth.users=${TRAEFIK_DASHBOARD_USER}:${TRAEFIK_DASHBOARD_HASH}"
      - "traefik.http.routers.traefik.middlewares=dashboard-auth"

  whoami:
    image: traefik/whoami:latest
    container_name: whoami
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`demo.example.com`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.tls.certresolver=letsencrypt"

Yukarıdaki örnekte dashboard’ı proxy.example.com üzerinden Basic Auth ile koruyoruz. whoami servisi ise demo.example.com üzerinden otomatik TLS ile yayınlanacak. Cloudflare tarafında bu iki alt alan adını A/AAAA kaydıyla sunucunuza yönlendirin. Cloudflare “turuncu bulut” (proxy) açık olabilir; DNS-01 challenge, proxied modda da sorunsuz çalışır.

Adım 3: Ortam değişkenleri ve parola karması

.env dosyanıza aşağıdaki gibi değerleri ekleyin. Basic Auth için bir bcrypt karma gerekir. Linux’ta hızla üretmek için şu komutu kullanabilirsiniz: htpasswd -nbB admin 'Sifre123' çıktısındaki admin:$2y$... kısmını kopyalayın.

CF_DNS_API_TOKEN=cf_api_tokeniniz_buraya
TRAEFIK_DASHBOARD_USER=admin
TRAEFIK_DASHBOARD_HASH=$2y$05$XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Adım 4: Servisleri başlatın

Klasörde docker compose up -d komutunu çalıştırın. İlk kurulumda Let’s Encrypt DNS-01 challenge ile TXT kaydı oluşturup doğrulama yapar ve acme.json içine sertifikaları yazar. Ardından https://demo.example.com adresini açtığınızda whoami çıktısını, https://proxy.example.com adresini açtığınızda ise kimlik doğrulama sonrası Traefik dashboard’ını görmelisiniz.

İpuçları ve iyi uygulamalar

- letsencrypt klasörünün izinlerini 600 veya 700 seviyesinde tutun; acme.json hassas bilgiler içerir.

- production ortamında traefik konteynerinin yalnızca ihtiyaç duyduğu ağlara eriştiğinden emin olun; Docker network’leri ile segmentasyon yapın.

- Rate limitlere takılmamak için Let’s Encrypt’in staging ortamını testlerde kullanabilirsiniz: --certificatesresolvers.letsencrypt.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory parametresini ekleyip denemeler tamamlanınca kaldırın.

- Cloudflare’de DNSSEC aktifse, zone yapılandırmanızın doğru olduğundan emin olun; yanlış DS kayıtları çözümleme sorunlarına neden olabilir.

Sık karşılaşılan hatalar ve çözümler

- Giriş başarısız: CF_DNS_API_TOKEN yetkileri eksik olabilir. Token’a Zone.DNS.Edit ve Zone.Read sağlandığından ve doğru zone’da sınırlandığından emin olun.

- Sertifika alınamıyor: Alan adınız doğru IP’ye işaret ediyor mu? DNS yayılımı tamamlandı mı? Ayrıca Cloudflare’de CNAME flattening ayarları bazen beklenmedik davranışlar gösterebilir; A kaydıyla test etmek daha sağlıklıdır.

- 404 veya yönlendirme döngüsü: http-catchall ve redirect-to-https etiketlerinin çakışmadığından emin olun. Aynı ana bilgisayar için hem http hem https router tanımlarını kontrol edin.

Sonuç

Bu kurulumla Docker tabanlı servisleriniz için modern, güvenli ve otomatik bir reverse proxy katmanı elde ettiniz. Traefik v3, Cloudflare DNS-01 ve Let’s Encrypt birleşimi; mikro servislerinizi hızlıca yayına almanızı, sertifikaları otomatik yenilemeyi ve tek noktadan görünürlük sağlamayı mümkün kılıyor. Bundan sonra yeni bir servis eklemek yalnızca birkaç label yazmak kadar basit. Üstelik, aynı mimariyi staging ve production çevrelerinde tekrar kullanarak sürdürülebilir bir dağıtım modeli kurabilirsiniz.

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.