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
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.
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.
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).
Recent Articles
📖
Yerel Uygulama Geçiş Anahtarları: Yerel ve WebView Uygulaması Karşılaştırması
👤
Geçiş Anahtarı Sorun Giderme: Geçiş Anahtarı Sorunları ve Hataları için Çözümler
👤
Windows'ta Geçiş Anahtarları Nasıl Etkinleştirilir
⚙️
WebAuthn Sanal Kimlik Doğrulayıcı ile Passkey'ler için Uçtan Uca Playwright Testi
⚙️
Passkey Eğitimi: Web Uygulamalarında Passkey Nasıl Uygulanı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.
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.
Ü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.
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:
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.
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.
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
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ınKoda 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ü:
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
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
'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.sqlCREATE 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.ymlversion: "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.
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.
Ö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:
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:
Bu geliştirmeler, uygulamanızın backend'inde passkey kimlik doğrulamasını etkinleştirmek için anahtardır. Bunları daha sonra kuracağız.
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.tsimport 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.
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.tsimport 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.tsimport 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;
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.tsimport { 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.tsimport { 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; } }, };
Hataları merkezi olarak işlemek ve ayrıca hata ayıklamayı kolaylaştırmak için bir
errorHandler.ts
dosyası ekliyoruz:
errorHandler.tsimport { 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.tsexport class CustomError extends Error { statusCode: number; constructor(message: string, statusCode: number = 500) { super(message); this.statusCode = statusCode; Object.setPrototypeOf(this, CustomError.prototype); } }
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.tsexport 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.tsexport const uint8ArrayToBase64 = (uint8Array: Uint8Array): string => Buffer.from(uint8Array).toString("base64"); export const base64ToUint8Array = (base64: string): Uint8Array => new Uint8Array(Buffer.from(base64, "base64"));
Ş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.tsimport { 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.name
) ve alan adını (rp.id
)
içerir.user.name
, user.id
ve user.displayName
gibi kullanıcı hesabı
ayrıntılarını içerir.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.tsimport { 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; };
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.
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.tsimport [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};
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.jsdocument.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; }
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:
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.ymlversion: "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):
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:
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.
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.
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
Table of Contents