Get your free and exclusive 80-page Banking Passkey Report
passkey tutorial how to implement passkeys

Passkey Eğitimi: Web Uygulamalarında Passkey Nasıl Uygulanır

Bu eğitim, web uygulamanızda geçiş anahtarlarını nasıl uygulayacağınızı açıklar. Node.js (TypeScript), SimpleWebAuthn, Vanilla HTML / JavaScript ve MySQL kullanıyoruz.

Vincent Delitz

Vincent

Created: June 17, 2025

Updated: June 20, 2025


We aim to make the Internet a safer place using passkeys. That's why we want to support developers with tutorials on how to implement passkeys.

1. Giriş: Passkey Nasıl Uygulanır#

Bu eğitimde, web sitenize passkey eklemek için adım adım bir kılavuz sunarak passkey uygulama çabalarınızda size yardımcı oluyoruz.

Demo Icon

Want to try passkeys yourself in a passkeys demo?

Try Passkeys

Harika bir web sitesi veya uygulama oluşturmak istediğinizde modern, sağlam ve kullanıcı dostu bir kimlik doğrulama yöntemine sahip olmak çok önemlidir. Passkey'ler bu zorluğun cevabı olarak ortaya çıkmıştır. Yeni giriş standardı olarak hizmet veren bu anahtarlar, geleneksel şifrelerin dezavantajlarından arınmış bir gelecek vaat ederek, gerçekten şifresiz bir giriş deneyimi sunar (bu sadece güvenli değil, aynı zamanda son derece kullanışlıdır).

Passkey'lerin potansiyelini gerçekten ifade eden şey, topladıkları destektir. Chrome, Firefox, Safari veya Edge gibi her önemli tarayıcı ve tüm önemli cihaz üreticileri (Apple, Microsoft, Google) desteği bünyesine katmıştır. Bu oybirliğiyle benimseme, passkey'lerin girişler için yeni standart olduğunu göstermektedir.

Evet, web uygulamalarına passkey entegrasyonu üzerine zaten eğitimler mevcut. React, Vue.js veya Next.js gibi frontend framework'leri için olsun, zorlukları azaltmak ve passkey uygulamalarınızı hızlandırmak için tasarlanmış çok sayıda kılavuz bulunmaktadır. Ancak, minimalist ve temel düzeyde kalan uçtan uca bir eğitim eksiktir. Birçok geliştirici bize ulaştı ve web uygulamaları için passkey uygulamasına açıklık getiren bir eğitim istedi.

İşte tam da bu yüzden bu kılavuzu hazırladık. Amacımız ne mi? Passkey'ler için frontend, backend ve veritabanı katmanını kapsayan (sonuncusu ciddi baş ağrılarına neden olabilmesine rağmen genellikle ihmal edilir) minimal ve uygulanabilir bir kurulum oluşturmak.

PasskeyAssessment Icon

Get a free passkey assessment in 15 minutes.

Book free consultation

Bu yolculuğun sonunda, şunları yapabileceğiniz minimal ve uygulanabilir bir web uygulaması oluşturmuş olacaksınız:

Acelesi olanlar veya bir referans isteyenler için, tüm kod tabanı GitHub üzerinde mevcuttur.

Sonucun nasıl göründüğünü merak ediyor musunuz? İşte nihai projenin bir önizlemesi (çok temel göründüğünü kabul ediyoruz ama ilginç kısım yüzeyin altında):

Kodun ve projenin bazı kısımlarının farklı veya daha sofistike bir şekilde yapılabileceğinin tamamen farkındayız, ancak biz temel unsurlara odaklanmak istedik. Bu yüzden kasıtlı olarak her şeyi basit ve passkey merkezli tuttuk.

StateOfPasskeys Icon

Want to find out how many people use passkeys?

View Adoption Data

Üretimdeki web siteme passkey'leri nasıl eklerim?

Bu, passkey kimlik doğrulaması için çok minimal bir örnektir. Aşağıdaki konular bu eğitimde dikkate alınmamış / uygulanmamıştır veya sadece çok temel düzeydedir:

Tüm bu özellikler için tam destek almak, çok daha fazla geliştirme çabası gerektirir. İlgilenenler için, bu passkey geliştirici yanılgıları makalesine bir göz atmalarını öneririz.

Slack Icon

Become part of our Passkeys Community for updates & support.

Join

2. Passkey Entegrasyonu İçin Ön Koşullar#

Passkey uygulamasına derinlemesine dalmadan önce, gerekli becerilere ve araçlara bir göz atalım. İşte başlamak için ihtiyacınız olanlar:

2.1 Frontend: Vanilla HTML & JavaScript#

Web'in yapı taşları olan HTML, CSS ve JavaScript hakkında sağlam bir kavrayış esastır. Herhangi bir modern JavaScript framework'ünden kaçınarak ve Vanilla JavaScript / HTML'e dayanarak işleri kasıtlı olarak basit tuttuk. Kullandığımız tek daha sofistike şey, WebAuthn sarmalayıcı kütüphanesi olan @simplewebauthn/browser'dır.

2.2 Backend: TypeScript'te Node.js (Express) + SimpleWebAuthn#

Backend'imiz için, TypeScript ile yazılmış bir Node.js (Express) sunucusu kullanıyoruz. Ayrıca SimpleWebAuthn'un WebAuthn sunucu uygulaması (@simplewebauthn/server ile birlikte @simplewebauthn/typescript-types) ile çalışmaya karar verdik. Çok sayıda WebAuthn sunucu uygulaması mevcuttur, bu yüzden elbette bunlardan herhangi birini de kullanabilirsiniz. TypeScript WebAuthn sunucusuna karar verdiğimiz için, temel Node.js ve npm bilgisi gereklidir.

2.3 Veritabanı: MySQL#

Tüm kullanıcı verileri ve passkey'lerin genel anahtarları bir veritabanında saklanır. Veritabanı teknolojisi olarak MySQL'i seçtik. MySQL ve ilişkisel veritabanları hakkında temel bir anlayış faydalıdır, ancak size tek tek adımlarda rehberlik edeceğiz.

İlerleyen kısımlarda, resmi olarak aynı anlama gelmeseler de WebAuthn ve passkey terimlerini sık sık birbirinin yerine kullanacağız. Özellikle kod kısmında daha iyi anlaşılması için bu varsayımı yapıyoruz.

Bu ön koşullar yerine getirildiğinde, passkey'lerin dünyasına dalmaya hazırsınız.

Ben Gould Testimonial

Ben Gould

Head of Engineering

I’ve built hundreds of integrations in my time, including quite a few with identity providers and I’ve never been so impressed with a developer experience as I have been with Corbado.

10.000'den fazla geliştirici Corbado'ya güveniyor ve passkey'ler ile interneti daha güvenli hale getiriyor. Sorularınız mı var? Passkey'ler üzerine 150'den fazla blog yazısı yazdık.

Passkeys Topluluğuna Katılın

3. Mimariye Genel Bakış: Passkey Örnek Uygulaması#

Koda ve yapılandırmalara geçmeden önce, kurmak istediğimiz sistemin mimarisine bir göz atalım. İşte kuracağımız mimarinin bir dökümü:

  • Frontend: Biri kullanıcı kaydı (bir passkey oluşturma) ve diğeri kimlik doğrulama (passkey kullanarak giriş yapma) için olmak üzere iki düğmeden oluşur.
  • Cihaz & Tarayıcı: Frontend'de bir eylem tetiklendiğinde, cihaz ve tarayıcı devreye girer. Kullanıcı ile backend arasında aracı olarak hareket ederek passkey'in oluşturulmasını ve doğrulanmasını kolaylaştırırlar.
  • Backend: Backend, uygulamamızda asıl sihrin gerçekleştiği yerdir. Frontend tarafından başlatılan tüm istekleri yönetir. Bu süreç, passkey'lerin oluşturulmasını ve doğrulanmasını içerir. Backend operasyonlarının merkezinde WebAuthn sunucusu bulunur. Adının aksine, bu bağımsız bir sunucu değildir. Bunun yerine, WebAuthn standardını uygulayan bir kütüphane veya pakettir. İki ana işlevi şunlardır: Kayıt (Sign-up), yeni kullanıcıların passkey'lerini oluşturduğu yer ve Kimlik Doğrulama (Login): Mevcut kullanıcıların passkey'lerini kullanarak giriş yaptığı yer. En basit haliyle, WebAuthn sunucusu, iki kategoriye ayrılmış dört genel API uç noktası sağlar: ikisi kayıt için ve ikisi kimlik doğrulama için. Bunlar, belirli bir formatta veri alacak şekilde tasarlanmıştır ve bu veriler daha sonra WebAuthn sunucusu tarafından işlenir. WebAuthn sunucusu, gerekli tüm kriptografik işlemlerden sorumludur. Unutulmaması gereken önemli bir husus, bu API uç noktalarının HTTPS üzerinden sunulması gerektiğidir.
  • MySQL Veritabanı: Depolama omurgamız olarak hareket eden MySQL veritabanı, kullanıcı verilerini ve bunlara karşılık gelen kimlik bilgilerini tutmaktan sorumludur.
Analyzer Icon

Are your users passkey-ready?

Test Passkey-Readiness

Bu mimari genel bakışıyla, uygulamamızın bileşenlerinin nasıl bir araya geldiğine dair kavramsal bir haritaya sahip olmalısınız. İlerledikçe, bu bileşenlerin her birine daha derinlemesine dalacak, kurulumlarını, yapılandırmalarını ve etkileşimlerini detaylandıracağız.

aşağıdaki şema, kayıt (sign-up) sırasındaki süreç akışını açıklamaktadır:

Aşağıdaki şema, kimlik doğrulama (login) sırasındaki süreç akışını açıklamaktadır:

Ayrıca, proje yapısını burada bulabilirsiniz (sadece en önemli dosyalar):

passkeys-tutorial ├── src # Tüm backend TypeScript kaynak kodunu içerir │ ├── controllers # Belirli istek türlerini işlemek için iş mantığı │ │ ├── authentication.ts # Passkey kimlik doğrulama mantığı │ │ └── registration.ts # Passkey kayıt mantığı │ ├── middleware │ │ ├── customError.ts # Standartlaştırılmış şekilde özel hata mesajları ekler │ │ └── errorHandler.ts # Genel hata işleyici │ ├── public │ │ ├── index.html # Frontend'deki ana HTML dosyası │ │ ├── css │ │ │ └── style.css # Temel stil │ │ └── js │ │ └── script.js # JavaScript mantığı (WebAuthn API dahil) │ ├── routes # API rotalarının ve işleyicilerinin tanımları │ │ └── routes.ts # Belirli passkey rotaları │ ├── services │ │ ├── credentialService.ts# Kimlik bilgileri tablosuyla etkileşime girer │ │ └── userService.ts # Kullanıcı tablosuyla etkileşime girer │ ├── utils # Yardımcı fonksiyonlar ve araçlar │ | ├── constants.ts # Bazı sabitler (ör. rpID) │ | └── utils.ts # Yardımcı fonksiyon │ ├── database.ts # Node.js'den MySQL veritabanına bağlantı oluşturur │ ├── index.ts # Node.js sunucusunun giriş noktası │ └── server.ts # Tüm sunucu ayarlarını yönetir ├── config.json # Node.js projesi için bazı yapılandırmalar ├── docker-compose.yml # Docker container'ları için servisleri, ağları ve hacimleri tanımlar ├── Dockerfile # Projenin bir Docker imajını oluşturur ├── init-db.sql # MySQL veritabanı şemamızı tanımlar ├── package.json # Node.js proje bağımlılıklarını ve betiklerini yönetir └── tsconfig.json # TypeScript'in kodunuzu nasıl derleyeceğini yapılandırır

4. MySQL Veritabanı Kurulumu#

Passkey'leri uygularken, veritabanı kurulumu önemli bir bileşendir. Yaklaşımımız, güvenilir test ve dağıtım için gerekli olan basit ve yalıtılmış bir ortam sunan, MySQL çalıştıran bir Docker container'ı kullanır.

Veritabanı şemamız, sadece iki tablo içeren kasıtlı olarak minimalisttir. Bu basitlik, daha net bir anlayışa ve daha kolay bakıma yardımcı olur.

Detaylı Tablo Yapısı

1. Credentials Tablosu: Passkey kimlik doğrulamasının merkezinde yer alan bu tablo, passkey kimlik bilgilerini saklar. Kritik Sütunlar:

  • credential_id: Her kimlik bilgisi için benzersiz bir tanımlayıcı. Bu alan için doğru veri türünü seçmek, biçimlendirme hatalarını önlemek için hayati önem taşır.
  • public_key: Her kimlik bilgisi için genel anahtarı saklar. credential_id'de olduğu gibi, uygun veri türü ve biçimlendirme çok önemlidir.

2. Users Tablosu: Kullanıcı hesaplarını ilgili kimlik bilgileriyle bağlar.

Deneyimlerimize ve diğer kütüphanelerin tavsiyelerine göre ilk tabloyu credentials olarak adlandırdığımızı unutmayın (SimpleWebAuthn'un authenticator veya authenticator_device olarak adlandırma önerisinin aksine).

credential_id ve public_key için veri türleri çok önemlidir. Hatalar genellikle yanlış veri türlerinden, kodlamadan veya biçimlendirmeden kaynaklanır (özellikle Base64 ve Base64URL arasındaki fark yaygın bir hata nedenidir), bu da tüm kayıt (sign-up) veya kimlik doğrulama (login) sürecini bozabilir.

Bu tabloları kurmak için gerekli tüm SQL komutları init-db.sql dosyasında bulunur. Bu betik, hızlı ve hatasız bir veritabanı başlatma sağlar.

Daha sofistike durumlar için, kimlik bilgileri hakkında daha fazla bilgi depolamak ve kullanıcı deneyimini iyileştirmek için credential_device_type veya credential_backed_up ekleyebilirsiniz. Ancak bu eğitimde bundan kaçınıyoruz.

init-db.sql
CREATE TABLE users ( id VARCHAR(255) PRIMARY KEY, username VARCHAR(255) NOT NULL UNIQUE ); CREATE TABLE credentials ( id INT AUTO_INCREMENT PRIMARY KEY, user_id VARCHAR(255) NOT NULL, credential_id VARCHAR(255) NOT NULL, public_key TEXT NOT NULL, counter INT NOT NULL, transports VARCHAR(255), FOREIGN KEY (user_id) REFERENCES users (id) );

Bu dosyayı oluşturduktan sonra, projenin kök düzeyinde yeni bir docker-compose.yml dosyası oluşturuyoruz:

docker-compose.yml
version: "3.1" services: db: image: mysql command: --default-authentication-plugin=mysql_native_password restart: always environment: MYSQL_ROOT_PASSWORD: my-secret-pw MYSQL_DATABASE: webauthn_db ports: - "3306:3306" volumes: - ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql

Bu dosya, MySQL veritabanını 3306 portunda başlatır ve tanımlanan veritabanı yapısını oluşturur. Burada kullanılan veritabanı adı ve şifresinin gösterim amacıyla basit tutulduğunu belirtmek önemlidir. Bir üretim ortamında, gelişmiş güvenlik için daha karmaşık kimlik bilgileri kullanmalısınız.

Sırada, Docker container'ımızı çalıştırmaya geçiyoruz. Bu noktada, docker-compose.yml dosyamız sadece bu tek container'ı içeriyor, ancak daha sonra daha fazla bileşen ekleyeceğiz. Container'ı başlatmak için aşağıdaki komutu kullanın:

docker compose up -d

Container çalışmaya başladıktan sonra, veritabanının beklendiği gibi çalışıp çalışmadığını doğrulamamız gerekiyor. Bir terminal açın ve MySQL veritabanıyla etkileşim kurmak için aşağıdaki komutu çalıştırın:

docker exec -it <container ID> mysql -uroot -p

Kök şifresini girmeniz istenecek, bu örnekte my-secret-pw'dir. Giriş yaptıktan sonra, webauthn_db veritabanını seçin ve bu komutları kullanarak tabloları görüntüleyin:

use webauthn_db; show tables;

Bu aşamada, betiğimizde tanımlanan iki tabloyu görmelisiniz. Başlangıçta, bu tablolar boş olacaktır, bu da veritabanı kurulumumuzun tamamlandığını ve passkey uygulama adımları için hazır olduğunu gösterir.

5. Passkey Uygulaması: Backend Entegrasyon Adımları#

Backend, herhangi bir passkey uygulamasının çekirdeğidir ve frontend'den gelen kullanıcı kimlik doğrulama isteklerini işlemek için merkezi bir merkez görevi görür. Kayıt (sign-up) ve kimlik doğrulama (login) isteklerini işlemek için WebAuthn sunucu kütüphanesi ile iletişim kurar ve kullanıcı kimlik bilgilerini depolamak ve almak için MySQL veritabanınızla etkileşime girer. Aşağıda, tüm istekleri işlemek için genel bir API sunacak olan TypeScript ile Node.js (Express) kullanarak backend'inizi kurma konusunda size rehberlik edeceğiz.

5.1 Node.js (Express) Sunucusunu Başlatma#

Öncelikle, projeniz için yeni bir dizin oluşturun ve terminalinizi veya komut isteminizi kullanarak bu dizine gidin.

Komutu çalıştırın

npx create-express-typescript-application passkeys-tutorial

Bu, daha sonraki uyarlamalar için kullanabileceğimiz, TypeScript ile yazılmış bir Node.js (Express) uygulamasının temel bir kod iskeletini oluşturur.

Projeniz, üzerine kurmamız gereken birkaç anahtar paket gerektirir:

  • @simplewebauthn/server: Kullanıcı kaydı (sign-up) ve kimlik doğrulama (login) gibi WebAuthn işlemlerini kolaylaştırmak için sunucu tarafı bir kütüphane.
  • express-session: Express.js için sunucu tarafı oturum verilerini depolayan ve çerezleri yöneten bir ara yazılım.
  • uuid: Uygulamalarda benzersiz anahtarlar veya tanımlayıcılar oluşturmak için yaygın olarak kullanılan evrensel olarak benzersiz tanımlayıcılar (UUID'ler) oluşturmak için bir yardımcı program.
  • mysql2: MySQL veritabanlarına bağlanma ve sorgu çalıştırma yetenekleri sağlayan bir Node.js istemcisi.

Yeni dizine geçin ve aşağıdaki komutlarla bunları yükleyin (gerekli TypeScript türlerini de yüklüyoruz):

cd passkeys-tutorial npm install @simplewebauthn/server mysql2 uuid express-session @types/express-session @types/uuid

Her şeyin doğru bir şekilde yüklendiğini doğrulamak için şunu çalıştırın

npm run dev:nodemon

Bu, herhangi bir dosya değişikliğinde sunucuyu otomatik olarak yeniden başlatan Nodemon ile Node.js sunucunuzu geliştirme modunda başlatmalıdır.

Sorun giderme ipucu: Hatalarla karşılaşırsanız, package.json dosyasındaki ts-node'u 10.8.1 sürümüne güncellemeyi deneyin ve ardından güncellemeleri yüklemek için npm i komutunu çalıştırın.

server.ts dosyanız, bir Express uygulaması için temel kurulumu ve ara yazılımı içerir. Passkey işlevselliğini entegre etmek için şunları eklemeniz gerekecektir:

  • Rotalar: Passkey kaydı (sign-up) ve kimlik doğrulama (login) için yeni rotalar tanımlayın.
  • Denetleyiciler: Bu rotalar için mantığı işlemek üzere denetleyiciler oluşturun.
  • Ara Yazılım: İstek ve hata işleme için ara yazılımı entegre edin.
  • Servisler: Veritabanında veri almak ve depolamak için servisler oluşturun.
  • Yardımcı Fonksiyonlar: Verimli kod işlemleri için yardımcı fonksiyonlar ekleyin.

Bu geliştirmeler, uygulamanızın backend'inde passkey kimlik doğrulamasını etkinleştirmek için anahtardır. Bunları daha sonra kuracağız.

Debugger Icon

Want to experiment with passkey flows? Try our Passkeys Debugger.

Try for Free

5.2 MySQL Veritabanı Bağlantısı#

Bölüm 4'te veritabanını oluşturup başlattıktan sonra, şimdi backend'imizin MySQL veritabanına bağlanabildiğinden emin olmamız gerekiyor. Bu nedenle, /src klasöründe yeni bir database.ts dosyası oluşturuyoruz ve aşağıdaki içeriği ekliyoruz:

database.ts
import mysql from "mysql2"; // Create a MySQL pool const pool = mysql.createPool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, waitForConnections: true, connectionLimit: 10, queueLimit: 0, }); // Promisify for Node.js async/await. export const promisePool = pool.promise();

Bu dosya daha sonra sunucumuz tarafından veritabanına erişmek için kullanılacaktır.

5.3 Uygulama Sunucusu Yapılandırması#

config.json dosyamıza kısa bir göz atalım, burada iki değişken zaten tanımlanmış: uygulamayı çalıştırdığımız port ve ortam:

config.json
{ "PORT": 8080, "NODE_ENV": "development" }

package.json olduğu gibi kalabilir ve şöyle görünmelidir:

package.json
{ "name": "passkeys-tutorial", "version": "0.0.1", "description": "passkeys-tutorial initialised with create-express-typescript-application.", "main": "src/index.ts", "scripts": { "build": "tsc", "start": "node ./build/src/index.js", "dev": "ts-node ./src/index.ts", "dev:nodemon": "nodemon -w src -e ts,json -x ts-node ./src/index.ts", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": ["express", "typescript"], "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/morgan": "^1.9.9", "@types/node": "^14.18.63", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.33.0", "eslint": "^7.32.0", "nodemon": "^2.0.22", "ts-node": "^10.8.1", "typescript": "^4.9.5" }, "dependencies": { "@simplewebauthn/server": "^8.3.5", "@types/express-session": "^1.17.10", "@types/uuid": "^9.0.7", "cors": "^2.8.5", "env-cmd": "^10.1.0", "express": "^4.18.2", "express-session": "^1.17.3", "fs": "^0.0.1-security", "helmet": "^4.6.0", "morgan": "^1.10.0", "mysql2": "^3.6.5", "uuid": "^9.0.1" } }

index.ts şöyle görünür:

index.ts
import app from "./server"; import config from "../config.json"; // Start the application by listening to specific port const port = Number(process.env.PORT || config.PORT || 8080); app.listen(port, () => { console.info("Express application started on port: " + port); });

server.ts dosyasında bazı şeyleri daha uyarlamamız gerekiyor. Ayrıca, kullanıcıların kimlik doğrulaması yapabileceği geçici sınamaları saklamak için bir tür geçici önbellek (ör. redis, memcache veya express-session) gereklidir. express-session kullanmaya karar verdik ve express-session ile işlerin yürümesi için express-session modülünü en üste tanımlıyoruz. Ek olarak, yönlendirmeyi düzenliyoruz ve şimdilik hata işlemeyi kaldırıyoruz (bu daha sonra ara yazılıma eklenecektir):

server.ts
import express, { Express } from "express"; import morgan from "morgan"; import helmet from "helmet"; import cors from "cors"; import config from "../config.json"; import { router as passkeyRoutes } from "./routes/routes"; import session from "express-session"; const app: Express = express(); declare module "express-session" { interface SessionData { currentChallenge?: string; loggedInUserId?: string; } } /************************************************************************************ * Basic Express Middlewares ***********************************************************************************/ app.set("json spaces", 4); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use( session({ // @ts-ignore secret: process.env.SESSION_SECRET, saveUninitialized: true, resave: false, cookie: { maxAge: 86400000, httpOnly: true, // Ensure to not expose session cookies to clientside scripts }, }), ); // Handle logs in console during development if (process.env.NODE_ENV === "development" || config.NODE_ENV === "development") { app.use(morgan("dev")); app.use(cors()); } // Handle security and origin in production if (process.env.NODE_ENV === "production" || config.NODE_ENV === "production") { app.use(helmet()); } /************************************************************************************ * Register all routes ***********************************************************************************/ app.use("/api/passkey", passkeyRoutes); app.use(express.static("src/public")); export default app;

5.4 Kimlik Bilgisi Servisi & Kullanıcı Servisi#

Oluşturduğumuz iki tablodaki verileri etkili bir şekilde yönetmek için, yeni bir src/services dizininde iki farklı servis geliştireceğiz: authenticatorService.ts ve userService.ts.

Her servis, veritabanıyla modüler ve organize bir şekilde etkileşim kurmamızı sağlayan CRUD (Oluşturma, Okuma, Güncelleme, Silme) metotlarını kapsayacaktır. Bu servisler, authenticator ve kullanıcı tablolarında veri depolamayı, almayı ve güncellemeyi kolaylaştıracaktır. İşte bu gerekli dosyaların yapısının nasıl olması gerektiği:

userService.ts şöyle görünür:

userService.ts
import { promisePool } from "../database"; // Adjust the import path as necessary import { v4 as uuidv4 } from "uuid"; export const userService = { async getUserById(userId: string) { const [rows] = await promisePool.query("SELECT * FROM users WHERE id = ?", [ userId, ]); // @ts-ignore return rows[0]; }, async getUserByUsername(username: string) { try { const [rows] = await promisePool.query( "SELECT * FROM users WHERE username = ?", [username], ); // @ts-ignore return rows[0]; } catch (error) { return null; } }, async createUser(username: string) { const id = uuidv4(); await promisePool.query("INSERT INTO users (id, username) VALUES (?, ?)", [ id, username, ]); return { id, username }; }, };

credentialService.ts aşağıdaki gibidir:

credentialService.ts
import { promisePool } from "../database"; import type { AuthenticatorDevice } from "@simplewebauthn/typescript-types"; export const credentialService = { async saveNewCredential( userId: string, credentialId: string, publicKey: string, counter: number, transports: string, ) { try { await promisePool.query( "INSERT INTO credentials (user_id, credential_id, public_key, counter, transports) VALUES (?, ?, ?, ?, ?)", [userId, credentialId, publicKey, counter, transports], ); } catch (error) { console.error("Error saving new credential:", error); throw error; } }, async getCredentialByCredentialId( credentialId: string, ): Promise<AuthenticatorDevice | null> { try { const [rows] = await promisePool.query( "SELECT * FROM credentials WHERE credential_id = ? LIMIT 1", [credentialId], ); // @ts-ignore if (rows.length === 0) return null; // @ts-ignore const row = rows[0]; return { userID: row.user_id, credentialID: row.credential_id, credentialPublicKey: row.public_key, counter: row.counter, transports: row.transports ? row.transports.split(",") : [], } as AuthenticatorDevice; } catch (error) { console.error("Error retrieving credential:", error); throw error; } }, async updateCredentialCounter(credentialId: string, newCounter: number) { try { await promisePool.query( "UPDATE credentials SET counter = ? WHERE credential_id = ?", [newCounter, credentialId], ); } catch (error) { console.error("Error updating credential counter:", error); throw error; } }, };

5.5 Ara Yazılım (Middleware)#

Hataları merkezi olarak işlemek ve ayrıca hata ayıklamayı kolaylaştırmak için bir errorHandler.ts dosyası ekliyoruz:

errorHandler.ts
import { Request, Response, NextFunction } from "express"; import { CustomError } from "./customError"; interface ErrorWithStatus extends Error { statusCode?: number; } export const handleError = ( err: CustomError, req: Request, res: Response, next: NextFunction, ) => { const statusCode = err.statusCode || 500; const message = err.message || "Internal Server Error"; console.log(message); res.status(statusCode).send({ error: message }); };

Bunun yanı sıra, daha sonra hataları daha hızlı bulmamıza yardımcı olacak özel hatalar oluşturabilmek istediğimiz için yeni bir customError.ts dosyası ekliyoruz:

customError.ts
export class CustomError extends Error { statusCode: number; constructor(message: string, statusCode: number = 500) { super(message); this.statusCode = statusCode; Object.setPrototypeOf(this, CustomError.prototype); } }

5.6 Yardımcı Programlar (Utilities)#

utils klasöründe, constants.ts ve utils.ts adında iki dosya oluşturuyoruz.

constant.ts, dayanan taraf (relying party) adı, dayanan taraf kimliği (relying party ID) ve origin gibi bazı temel WebAuthn sunucu bilgilerini tutar:

constant.ts
export const rpName: string = "Passkeys Tutorial"; export const rpID: string = "localhost"; export const origin: string = `http://${rpID}:8080`;

utils.ts, daha sonra verileri kodlamak ve kodunu çözmek için ihtiyaç duyacağımız iki fonksiyonu tutar:

utils.ts
export const uint8ArrayToBase64 = (uint8Array: Uint8Array): string => Buffer.from(uint8Array).toString("base64"); export const base64ToUint8Array = (base64: string): Uint8Array => new Uint8Array(Buffer.from(base64, "base64"));

5.7 SimpleWebAuthn ile Passkey Denetleyicileri#

Şimdi, backend'imizin kalbine geliyoruz: denetleyiciler. Yeni bir passkey oluşturmak için bir (registration.ts) ve bir passkey ile giriş yapmak için bir (authentication.ts) olmak üzere iki denetleyici oluşturuyoruz.

registration.ts şöyle görünür:

registration.ts
import { generateRegistrationOptions, verifyRegistrationResponse, } from "@simplewebauthn/server"; import { uint8ArrayToBase64 } from "../utils/utils"; import { rpName, rpID, origin } from "../utils/constants"; import { credentialService } from "../services/credentialService"; import { userService } from "../services/userService"; import { RegistrationResponseJSON } from "@simplewebauthn/typescript-types"; import { Request, Response, NextFunction } from "express"; import { CustomError } from "../middleware/customError"; export const handleRegisterStart = async ( req: Request, res: Response, next: NextFunction, ) => { const { username } = req.body; if (!username) { return next(new CustomError("Username empty", 400)); } try { let user = await userService.getUserByUsername(username); if (user) { return next(new CustomError("User already exists", 400)); } else { user = await userService.createUser(username); } const options = await generateRegistrationOptions({ rpName, rpID, userID: user.id, userName: user.username, timeout: 60000, attestationType: "direct", excludeCredentials: [], authenticatorSelection: { residentKey: "preferred", }, // Support for the two most common algorithms: ES256, and RS256 supportedAlgorithmIDs: [-7, -257], }); req.session.loggedInUserId = user.id; req.session.currentChallenge = options.challenge; res.send(options); } catch (error) { next( error instanceof CustomError ? error : new CustomError("Internal Server Error", 500), ); } }; export const handleRegisterFinish = async ( req: Request, res: Response, next: NextFunction, ) => { const { body } = req; const { currentChallenge, loggedInUserId } = req.session; if (!loggedInUserId) { return next(new CustomError("User ID is missing", 400)); } if (!currentChallenge) { return next(new CustomError("Current challenge is missing", 400)); } try { const verification = await verifyRegistrationResponse({ response: body as RegistrationResponseJSON, expectedChallenge: currentChallenge, expectedOrigin: origin, expectedRPID: rpID, requireUserVerification: true, }); if (verification.verified && verification.registrationInfo) { const { credentialPublicKey, credentialID, counter } = verification.registrationInfo; await credentialService.saveNewCredential( loggedInUserId, uint8ArrayToBase64(credentialID), uint8ArrayToBase64(credentialPublicKey), counter, body.response.transports, ); res.send({ verified: true }); } else { next(new CustomError("Verification failed", 400)); } } catch (error) { next( error instanceof CustomError ? error : new CustomError("Internal Server Error", 500), ); } finally { req.session.loggedInUserId = undefined; req.session.currentChallenge = undefined; } };

WebAuthn kayıt (sign-up) sürecindeki iki anahtar uç noktayı yöneten denetleyicilerimizin işlevlerini gözden geçirelim. Bu aynı zamanda şifre tabanlı kimlik doğrulamasına göre en büyük farklardan birinin yattığı yerdir: Her kayıt (sign-up) veya kimlik doğrulama (login) denemesi için, arada belirli frontend içeriği gerektiren iki backend API çağrısı gereklidir. Şifreler genellikle sadece bir uç noktaya ihtiyaç duyar.

1. handleRegisterStart Uç Noktası:

Bu uç nokta, frontend tarafından tetiklenir ve yeni bir passkey ve hesap oluşturmak için bir kullanıcı adı alır. Bu örnekte, yalnızca henüz mevcut bir hesap yoksa yeni bir hesap / passkey oluşturulmasına izin veriyoruz. Gerçek dünya uygulamalarında, bunu kullanıcılara zaten bir passkey'in mevcut olduğu ve aynı cihazdan eklemenin mümkün olmadığı (ancak kullanıcı bir tür onaydan sonra farklı bir cihazdan passkey ekleyebilir) şeklinde ele almanız gerekir. Basitlik adına, bu eğitimde bunu göz ardı ediyoruz.

PublicKeyCredentialCreationOptions hazırlanır. residentKey tercih edilen olarak ayarlanır ve attestationType direct olarak ayarlanır, potansiyel veritabanı depolaması için authenticator'dan daha fazla veri toplanır.

Genel olarak, PublicKeyCredentialCreationOptions aşağıdaki verilerden oluşur:

dictionary [PublicKeyCredentialCreationOptions](/glossary/publickeycredentialcreationoptions) { required PublicKeyCredentialRpEntity rp; required PublicKeyCredentialUserEntity user; required BufferSource challenge; required sequence<PublicKeyCredentialParameters> pubKeyCredParams; unsigned long timeout; sequence<PublicKeyCredentialDescriptor> [excludeCredentials](/glossary/excludecredentials) = []; AuthenticatorSelectionCriteria authenticatorSelection; DOMString attestation = "none"; AuthenticationExtensionsClientInputs extensions; };
  • rp: Dayanan taraf (relying party) (web sitesi veya hizmet) bilgilerini temsil eder, genellikle adını (rp.name) ve alan adını (rp.id) içerir.
  • user: user.name, user.id ve user.displayName gibi kullanıcı hesabı ayrıntılarını içerir.
  • challenge: Kayıt işlemi sırasında tekrar saldırılarını önlemek için WebAuthn sunucusu tarafından oluşturulan güvenli, rastgele bir değer.
  • pubKeyCredParams: Kullanılan kriptografik algoritma da dahil olmak üzere oluşturulacak genel anahtar kimlik bilgisi türünü belirtir (daha fazlasını okuyun).
  • timeout: İsteğe bağlı, kullanıcının etkileşimi tamamlaması için milisaniye cinsinden süreyi ayarlar.
  • excludeCredentials: Hariç tutulacak kimlik bilgileri listesi; aynı cihaz / authenticator için birden çok kez passkey kaydını önlemek için kullanılır (daha fazlasını okuyun).
  • authenticatorSelection: Kullanıcı doğrulamasını destekleyip desteklemediği veya yerleşik anahtarların nasıl teşvik edilmesi gerektiği gibi authenticator'ı seçme kriterleri (daha fazlasını okuyun).
  • attestation: "none", "indirect" veya "direct" gibi istenen attestation iletim tercihini belirtir (daha fazlasını okuyun).
  • extensions: İsteğe bağlı, ek istemci uzantılarına izin verir.

Kullanıcı Kimliği (User ID) ve sınama (challenge) bir oturum nesnesinde saklanır, bu da eğitim amaçları için süreci basitleştirir. Ayrıca, her kayıt (sign-up) veya kimlik doğrulama (login) denemesinden sonra oturum temizlenir.

2. handleRegisterFinish Uç Noktası:

Bu uç nokta, daha önce ayarlanan kullanıcı kimliğini (user ID) ve sınamayı (challenge) alır. RegistrationResponse'u sınama ile doğrular. Geçerliyse, kullanıcı için yeni bir kimlik bilgisi depolar. Veritabanında saklandıktan sonra, kullanıcı kimliği (user ID) ve sınama oturumdan kaldırılır.

İpucu: Uygulamanızı hata ayıklarken, Chrome'u tarayıcı olarak ve passkey tabanlı uygulamaların geliştirici deneyimini iyileştirmek için yerleşik özelliklerini, örneğin sanal WebAuthn kimlik doğrulayıcısı ve cihaz günlüğü (daha fazla bilgi için aşağıdaki geliştiriciler için ek passkey ipuçları bölümüne bakın) kullanmanızı şiddetle tavsiye ederiz.

Sırada, benzer bir yapıya ve işlevselliğe sahip olan authentication.ts'e geçiyoruz.

authentication.ts şöyle görünür:

authentication.ts
import { Request, Response, NextFunction } from "express"; import { generateAuthenticationOptions, verifyAuthenticationResponse, } from "@simplewebauthn/server"; import { uint8ArrayToBase64, base64ToUint8Array } from "../utils/utils"; import { rpID, origin } from "../utils/constants"; import { credentialService } from "../services/credentialService"; import { userService } from "../services/userService"; import { AuthenticatorDevice } from "@simplewebauthn/typescript-types"; import { isoBase64URL } from "@simplewebauthn/server/helpers"; import { VerifiedAuthenticationResponse, VerifyAuthenticationResponseOpts, } from "@simplewebauthn/server/esm"; import { CustomError } from "../middleware/customError"; export const handleLoginStart = async ( req: Request, res: Response, next: NextFunction, ) => { const { username } = req.body; try { const user = await userService.getUserByUsername(username); if (!user) { return next(new CustomError("User not found", 404)); } req.session.loggedInUserId = user.id; // [allowCredentials](/glossary/allowcredentials) is purposely for this demo left empty. This causes all existing local credentials // to be displayed for the service instead only the ones the username has registered. const options = await generateAuthenticationOptions({ timeout: 60000, allowCredentials: [], userVerification: "required", rpID, }); req.session.currentChallenge = options.challenge; res.send(options); } catch (error) { next( error instanceof CustomError ? error : new CustomError("Internal Server Error", 500), ); } }; export const handleLoginFinish = async ( req: Request, res: Response, next: NextFunction, ) => { const { body } = req; const { currentChallenge, loggedInUserId } = req.session; if (!loggedInUserId) { return next(new CustomError("User ID is missing", 400)); } if (!currentChallenge) { return next(new CustomError("Current challenge is missing", 400)); } try { const credentialID = isoBase64URL.toBase64(body.rawId); const bodyCredIDBuffer = isoBase64URL.toBuffer(body.rawId); const dbCredential: AuthenticatorDevice | null = await credentialService.getCredentialByCredentialId(credentialID); if (!dbCredential) { return next(new CustomError("Credential not registered with this site", 404)); } // @ts-ignore const user = await userService.getUserById(dbCredential.userID); if (!user) { return next(new CustomError("User not found", 404)); } // @ts-ignore dbCredential.credentialID = base64ToUint8Array(dbCredential.credentialID); // @ts-ignore dbCredential.credentialPublicKey = base64ToUint8Array( dbCredential.credentialPublicKey, ); let verification: VerifiedAuthenticationResponse; const opts: VerifyAuthenticationResponseOpts = { response: body, expectedChallenge: currentChallenge, expectedOrigin: origin, expectedRPID: rpID, authenticator: dbCredential, }; verification = await verifyAuthenticationResponse(opts); const { verified, authenticationInfo } = verification; if (verified) { await credentialService.updateCredentialCounter( uint8ArrayToBase64(bodyCredIDBuffer), authenticationInfo.newCounter, ); res.send({ verified: true }); } else { next(new CustomError("Verification failed", 400)); } } catch (error) { next( error instanceof CustomError ? error : new CustomError("Internal Server Error", 500), ); } finally { req.session.currentChallenge = undefined; req.session.loggedInUserId = undefined; } };

Kimlik doğrulama (login) sürecimiz iki uç nokta içerir:

1. handleLoginStart Uç Noktası:

Bu uç nokta, bir kullanıcı giriş yapmaya çalıştığında etkinleştirilir. Önce kullanıcı adının veritabanında olup olmadığını kontrol eder, bulunamazsa bir hata döndürür. Gerçek dünya senaryosunda, bunun yerine yeni bir hesap oluşturmayı teklif edebilirsiniz.

Mevcut kullanıcılar için, veritabanından kullanıcı kimliğini alır, oturumda saklar ve PublicKeyCredentialRequestOptions seçenekleri oluşturur. allowCredentials kimlik bilgisi kullanımını kısıtlamamak için boş bırakılmıştır. Bu nedenle, bu dayanan taraf (relying party) için mevcut tüm passkey'ler passkey modalında seçilebilir.

Oluşturulan sınama (challenge) da oturumda saklanır ve PublicKeyCredentialRequestOptions frontend'e geri gönderilir.

PublicKeyCredentialRequestOptions aşağıdaki verilerden oluşur:

dictionary [PublicKeyCredentialRequestOptions](/glossary/publickeycredentialrequestoptions) { required BufferSource challenge; unsigned long timeout; USVString rpId; sequence<PublicKeyCredentialDescriptor> [allowCredentials](/glossary/allowcredentials) = []; DOMString userVerification = "preferred"; AuthenticationExtensionsClientInputs extensions; };
  • challenge: Kimlik doğrulama işlemi sırasında tekrar saldırılarını önlemek için WebAuthn sunucusundan gelen güvenli, rastgele bir değer.
  • timeout: İsteğe bağlı, kullanıcının kimlik doğrulama isteğine yanıt vermesi için milisaniye cinsinden süreyi ayarlar.
  • rpId: Dayanan taraf kimliği, genellikle hizmetin alan adı.
  • allowCredentials: Bu kimlik doğrulama (login) için hangi kimlik bilgilerinin kullanılabileceğini belirten isteğe bağlı bir kimlik bilgisi tanımlayıcıları listesi.
  • userVerification: "required", "preferred" veya "discouraged" gibi kullanıcı doğrulama gereksinimini belirtir.
  • extensions: İsteğe bağlı, ek istemci uzantılarına izin verir.

2. handleLoginFinish Uç Noktası:

Bu uç nokta, oturumdan currentChallenge ve loggedInUserId'yi alır.

Gövdeden gelen kimlik bilgisi kimliğini (credential ID) kullanarak doğru kimlik bilgisi için veritabanını sorgular. Kimlik bilgisi bulunursa, bu, bu kimlik bilgisi kimliği (credential ID) ile ilişkili kullanıcının artık kimliğinin doğrulanabileceği (giriş yapabileceği) anlamına gelir. Ardından, kimlik bilgisinden aldığımız kullanıcı kimliği aracılığıyla kullanıcı tablosundan kullanıcıyı sorgulayabilir ve sınama (challenge) ve istek gövdesini kullanarak authenticationResponse'u doğrulayabiliriz. Her şey başarılı olursa, giriş başarı mesajını gösteririz. Eşleşen bir kimlik bilgisi bulunmazsa, bir hata gönderilir.

Ek olarak, doğrulama başarılı olursa, kimlik bilgisinin sayacı güncellenir, kullanılan sınama ve loggedInUserId oturumdan kaldırılır.

Bunun üzerine, src/app ve src/constant klasörünü içindeki tüm dosyalarla birlikte silebiliriz.

Not: Gerçek hayattaki uygulamalarda çok önemli olan uygun oturum yönetimi ve rota koruması, bu eğitimde basitlik adına atlanmıştır.

5.8 Passkey Rotaları#

Son olarak, denetleyicilerimizin yeni bir src/routes dizininde bulunan routes.ts dosyasına uygun rotaları ekleyerek ulaşılabilir olduğundan emin olmamız gerekiyor:

routes.ts
import [express](/blog/ nodejs - passkeys ) from 'express'; import {handleError} from '../middleware/errorHandler'; import {handleRegisterStart, handleRegisterFinish} from '../controllers/registration'; import {handleLoginStart, handleLoginFinish} from '../controllers/authentication'; const router = express.Router(); router.post('/registerStart', handleRegisterStart); router.post('/registerFinish', handleRegisterFinish); router.post('/loginStart', handleLoginStart); router.post('/loginFinish', handleLoginFinish); router.use(handleError); export {router};
Substack Icon

Subscribe to our Passkeys Substack for the latest news.

Subscribe

6. Passkey'leri Frontend'e Entegre Etme#

Passkey eğitiminin bu bölümü, uygulamanızın frontend'inde passkey'leri nasıl destekleyeceğinize odaklanmaktadır. Üç dosyadan oluşan çok temel bir frontend'imiz var: index.html, styles.css ve script.js. Üç dosya da yeni bir src/public klasöründedir.

index.html dosyası, kullanıcı adı için bir giriş alanı ve kayıt olmak ve giriş yapmak için iki düğme içerir. Ayrıca, js/script.js dosyasında tarayıcı Web Authentication API ile etkileşimi basitleştiren @simplewebauthn/browser betiğini içe aktarıyoruz.

index.html şöyle görünür:

index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Passkey Tutorial</title> <link rel="stylesheet" href="css/style.css" /> </head> <body> <div class="container"> <h1>Passkey Tutorial</h1> <div id="message"></div> <div class="input-group"> <input type="text" id="username" placeholder="Enter username" /> <button id="registerButton">Register</button> <button id="loginButton">Login</button> </div> </div> <script src="https://unpkg.com/@simplewebauthn/browser/dist/bundle/index.es5.umd.min.js"></script> <script src="js/script.js"></script> </body> </html>

script.js aşağıdaki gibidir:

script.js
document.getElementById("registerButton").addEventListener("click", register); document.getElementById("loginButton").addEventListener("click", login); function showMessage(message, isError = false) { const messageElement = document.getElementById("message"); messageElement.textContent = message; messageElement.style.color = isError ? "red" : "green"; } async function register() { // Retrieve the username from the input field const username = document.getElementById("username").value; try { // Get registration options from your server. Here, we also receive the challenge. const response = await fetch("/api/passkey/registerStart", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: username }), }); console.log(response); // Check if the registration options are ok. if (!response.ok) { throw new Error( "User already exists or failed to get registration options from server", ); } // Convert the registration options to JSON. const options = await response.json(); console.log(options); // This triggers the browser to display the passkey / WebAuthn modal (e.g. Face ID, Touch ID, Windows Hello). // A new attestation is created. This also means a new public-private-key pair is created. const attestationResponse = await SimpleWebAuthnBrowser.startRegistration(options); // Send attestationResponse back to server for verification and storage. const verificationResponse = await fetch("/api/passkey/registerFinish", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(attestationResponse), }); if (verificationResponse.ok) { showMessage("Registration successful"); } else { showMessage("Registration failed", true); } } catch (error) { showMessage("Error: " + error.message, true); } } async function login() { // Retrieve the username from the input field const username = document.getElementById("username").value; try { // Get login options from your server. Here, we also receive the challenge. const response = await fetch("/api/passkey/loginStart", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: username }), }); // Check if the login options are ok. if (!response.ok) { throw new Error("Failed to get login options from server"); } // Convert the login options to JSON. const options = await response.json(); console.log(options); // This triggers the browser to display the passkey / WebAuthn modal (e.g. Face ID, Touch ID, Windows Hello). // A new assertionResponse is created. This also means that the challenge has been signed. const assertionResponse = await SimpleWebAuthnBrowser.startAuthentication(options); // Send assertionResponse back to server for verification. const verificationResponse = await fetch("/api/passkey/loginFinish", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(assertionResponse), }); if (verificationResponse.ok) { showMessage("Login successful"); } else { showMessage("Login failed", true); } } catch (error) { showMessage("Error: " + error.message, true); } }

script.js'de üç ana fonksiyon bulunmaktadır:

1. showMessage Fonksiyonu:

Bu, öncelikle hata mesajlarını görüntülemek için kullanılan ve hata ayıklamaya yardımcı olan bir yardımcı fonksiyondur.

2. Register Fonksiyonu:

Kullanıcı "Register" düğmesine tıkladığında tetiklenir. Giriş alanından kullanıcı adını alır ve passkeyRegisterStart uç noktasına gönderir. Yanıt, JSON'a dönüştürülen ve SimpleWebAuthnBrowser.startRegistration'a geçirilen PublicKeyCredentialCreationOptions'ı içerir. Bu çağrı, cihaz authenticator'ını (Face ID veya Touch ID gibi) etkinleştirir. Başarılı yerel kimlik doğrulamasının ardından, imzalanan sınama (challenge) passkeyRegisterFinish uç noktasına geri gönderilir ve passkey oluşturma işlemi tamamlanır.

Kayıt (sign-up) işlemi sırasında, attestation nesnesi çok önemli bir rol oynar, bu yüzden ona daha yakından bakalım.

Attestation nesnesi temel olarak üç bileşenden oluşur: fmt, attStmt ve authData. fmt öğesi, attestation ifadesinin biçimini belirtirken, attStmt gerçek attestation ifadesini temsil eder. Attestation'ın gereksiz görüldüğü senaryolarda, fmt "none" olarak belirlenir ve bu da boş bir attStmt ile sonuçlanır.

Odak noktası, bu yapı içindeki authData segmentidir. Bu segment, sunucumuzda dayanan taraf kimliği, bayraklar, sayaç ve onaylanmış kimlik bilgisi verileri gibi temel unsurları almak için anahtardır. Bayraklarla ilgili olarak, özellikle ilgi çekici olanlar, bir passkey'in senkronize edilip edilmediği hakkında daha fazla bilgi sağlayan BS (Yedekleme Durumu) ve BE (Yedekleme Uygunluğu)'dir (örneğin, iCloud Keychain veya 1Password aracılığıyla). Ayrıca, UV (Kullanıcı Doğrulama) ve UP (Kullanıcı Varlığı) daha faydalı bilgiler sağlar.

Kimlik doğrulayıcı verileri, dayanan taraf kimliği (relying party ID) ve attestation ifadesi de dahil olmak üzere attestation nesnesinin çeşitli bölümlerinin, authenticator tarafından özel anahtarı kullanılarak ya hash'lendiğini ya da dijital olarak imzalandığını belirtmek önemlidir. Bu süreç, attestation nesnesinin genel bütünlüğünü korumak için ayrılmaz bir parçasıdır.

3. Login Fonksiyonu:

Kullanıcı "Login" düğmesine tıkladığında etkinleştirilir. Kayıt fonksiyonuna benzer şekilde, kullanıcı adını alır ve passkeyLoginStart uç noktasına gönderir. PublicKeyCredentialRequestOptions içeren yanıt, JSON'a dönüştürülür ve SimpleWebAuthnBrowser.startAuthentication ile kullanılır. Bu, cihazda yerel kimlik doğrulamasını tetikler. İmzalanan sınama (challenge) daha sonra passkeyLoginFinish uç noktasına geri gönderilir. Bu uç noktadan gelen başarılı bir yanıt, kullanıcının uygulamaya başarıyla giriş yaptığını gösterir.

Ayrıca, eşlik eden CSS dosyası uygulama için basit bir stil sağlar:

body { font-family: "Helvetica Neue", Arial, sans-serif; text-align: center; padding: 40px; background-color: #f3f4f6; color: #333; } .container { max-width: 400px; margin: auto; background: white; padding: 20px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); border-radius: 8px; } h1 { color: #007bff; font-size: 24px; margin-bottom: 20px; } .input-group { margin-bottom: 20px; } input[type="text"] { padding: 10px; margin-bottom: 10px; border: 1px solid #ced4da; border-radius: 4px; width: calc(100% - 22px); } button { width: calc(50% - 20px); padding: 10px 0; margin: 5px; font-size: 16px; cursor: pointer; border: none; border-radius: 4px; background-color: #007bff; color: white; } button:hover { background-color: #0056b3; } #message { color: #dc3545; margin: 20px; }

7. Passkey Örnek Uygulamasını Çalıştırma#

Uygulamanızı çalışır durumda görmek için, TypeScript kodunuzu şununla derleyin ve çalıştırın:

npm run dev

Sunucunuz şimdi http://localhost:8080 adresinde çalışıyor olmalıdır.

Üretim İçin Dikkat Edilmesi Gerekenler:

Unutmayın, ele aldığımız temel bir taslaktı. Bir passkey uygulamasını üretim ortamında dağıtırken, daha derine inmeniz gerekir:

  • Güvenlik Önlemleri: Kullanıcı verilerini korumak için sağlam güvenlik uygulamaları uygulayın.
  • Hata Yönetimi: Uygulamanızın hataları zarif bir şekilde ele aldığından ve günlüğe kaydettiğinden emin olun.
  • Veritabanı Yönetimi: Ölçeklenebilirlik ve güvenilirlik için veritabanı işlemlerini optimize edin.

8. Passkey DevOps Entegrasyonu#

Veritabanımız için zaten bir Docker container'ı kurduk. Sırada, Docker Compose kurulumumuzu hem backend hem de frontend ile sunucuyu içerecek şekilde genişleteceğiz. docker-compose.yml dosyanız buna göre güncellenmelidir.

Uygulamamızı container'laştırmak için, gerekli paketleri yükleyen ve geliştirme sunucusunu başlatan yeni bir Dockerfile oluşturuyoruz:

Docker
# Use an official Node runtime as a parent image FROM node:20-alpine # Set the working directory in the container WORKDIR /usr/src/app # Copy package.json and package-lock.json COPY package*.json ./ # Install any needed packages RUN npm install # Bundle your app's source code inside the Docker image COPY . . # Make port 8080 available to the world outside this container EXPOSE 8080 # Define the command to run your app CMD ["npm", "run", "dev"]

Daha sonra, bu container'ı başlatmak için docker-compose.yml dosyasını da genişletiyoruz:

docker-compose.yml
version: "3.1" services: db: image: mysql command: --default-authentication-plugin=mysql_native_password restart: always environment: MYSQL_ROOT_PASSWORD: my-secret-pw MYSQL_DATABASE: webauthn_db ports: - "3306:3306" volumes: - ./init-db.sql:/docker-entrypoint-initdb.d/init-db.sql app: build: . ports: - "8080:8080" environment: - DB_HOST=db - DB_USER=root - DB_PASSWORD=my-secret-pw - DB_NAME=webauthn_db - SESSION_SECRET=secret123 depends_on: - db

Şimdi terminalinizde docker compose up komutunu çalıştırır ve http://localhost:8080 adresine erişirseniz, passkey web uygulamanızın çalışan sürümünü görmelisiniz (burada Windows 11 23H2 + Chrome 119'da çalışıyor):

9. Geliştiriciler İçin Ek Passkey İpuçları#

Bir süredir passkey uygulamaları üzerinde çalıştığımız için, gerçek hayattaki passkey uygulamaları üzerinde çalışırken birkaç zorlukla karşılaştık:

  • Cihaz / platform uyumluluğu ve desteği
  • Kullanıcı katılımı ve eğitimi
  • Kaybolan veya değiştirilen cihazların yönetimi
  • Çapraz platform kimlik doğrulaması
  • Yedek mekanizmalar
  • Kodlama karmaşıklığı: Kodlama genellikle en zor kısımdır çünkü JSON, CBOR, uint8array'ler, buffer'lar, blob'lar, farklı veritabanları, base64 ve base64url ile uğraşmanız gerekir ve burada birçok hata meydana gelebilir
  • Passkey yönetimi (örneğin, passkey eklemek, silmek veya yeniden adlandırmak için)

Ayrıca, uygulama kısmına gelince geliştiriciler için aşağıdaki ipuçlarımız var:

Passkeys Hata Ayıklayıcısını Kullanın

Passkeys hata ayıklayıcısı, farklı WebAuthn sunucu ayarlarını ve istemci yanıtlarını test etmeye yardımcı olur. Ayrıca, kimlik doğrulayıcı yanıtları için harika bir ayrıştırıcı sağlar.

Chrome Cihaz Günlüğü Özelliği ile Hata Ayıklama

FIDO/WebAuthn çağrılarını izlemek için Chrome'un cihaz günlüğünü (chrome://device-log/ üzerinden erişilebilir) kullanın. Bu özellik, kimlik doğrulama (login) sürecinin gerçek zamanlı günlüklerini sağlar, bu da değiş tokuş edilen verileri görmenize ve ortaya çıkan sorunları gidermenize olanak tanır.

Chrome'daki tüm passkey'lerinizi almak için bir başka çok kullanışlı kısayol da chrome://settings/passkeys kullanmaktır.

Chrome Sanal WebAuthn Kimlik Doğrulayıcısını Kullanın

Geliştirme sırasında Touch ID, Face ID veya Windows Hello istemini kullanmaktan kaçınmak için, Chrome gerçek bir kimlik doğrulayıcıyı taklit eden çok kullanışlı bir sanal WebAuthn kimlik doğrulayıcısı ile birlikte gelir. İşleri hızlandırmak için bunu kullanmanızı şiddetle tavsiye ederiz. Daha fazla ayrıntıyı burada bulabilirsiniz.

Farklı Platformlarda ve Tarayıcılarda Test Edin

Çeşitli tarayıcılar ve platformlar arasında uyumluluk ve işlevsellik sağlayın. WebAuthn farklı tarayıcılarda farklı davranır, bu nedenle kapsamlı testler anahtardır.

Farklı Cihazlarda Test Edin

Burada, yerel uygulamanızı diğer (mobil) cihazlarda ulaşılabilir hale getirebileceğiniz ngrok gibi araçlarla çalışmak özellikle yararlıdır.

Kullanıcı Doğrulamasını Tercih Edilen Olarak Ayarlayın

PublicKeyCredentialRequestOptions'daki userVerification için özellikleri tanımlarken, kullanılabilirlik ve güvenlik arasında iyi bir denge olduğu için bunları tercih edilen olarak ayarlamayı seçin. Bu, uygun cihazlarda güvenlik kontrollerinin yerinde olduğu, biyometrik yetenekleri olmayan cihazlarda ise kullanıcı dostluğunun korunduğu anlamına gelir.

10. Sonuç: Passkey Eğitimi#

Bu passkey eğitiminin, passkey'lerin nasıl etkili bir şekilde uygulanacağına dair net bir anlayış sağladığını umuyoruz. Eğitim boyunca, temel kavramlara ve pratik uygulamaya odaklanarak bir passkey oluşturma uygulaması için gerekli adımları inceledik. Bu kılavuz bir başlangıç noktası olarak hizmet etse de, WebAuthn dünyasında keşfedilecek ve geliştirilecek çok daha fazla şey var.

Geliştiricileri, passkey'lerin nüanslarına daha derinlemesine dalmaya (örneğin, birden fazla passkey ekleme, cihazlarda passkey hazırlığını kontrol etme veya kurtarma çözümleri sunma) teşvik ediyoruz. Bu, hem zorluklar hem de kullanıcı kimlik doğrulamasını geliştirmede büyük ödüller sunan, başlamaya değer bir yolculuktur. Passkey'ler ile sadece bir özellik oluşturmuyorsunuz; daha güvenli ve kullanıcı dostu bir dijital dünyaya katkıda bulunuyorsunuz.

Add passkeys to your app in <1 hour with our UI components, SDKs & guides.

Start for free

Share this article


LinkedInTwitterFacebook

Enjoyed this read?

🤝 Join our Passkeys Community

Share passkeys implementation tips and get support to free the world from passwords.

🚀 Subscribe to Substack

Get the latest news, strategies, and insights about passkeys sent straight to your inbox.

Related Articles