OpenID4VCI protokolünü kullanarak bir W3C Doğrulanabilir Kimlik Bilgisi vericisi oluşturmayı öğrenin. Bu adım adım kılavuz, dijital cüzdanlarla uyumlu, kriptografik olarak imzalanmış kimlik bilgileri veren bir Next.js uygulamasını nasıl oluşturacağınızı g
Amine
Created: August 20, 2025
Updated: August 21, 2025
See the original blog version in English here.
Dijital Kimlik Bilgileri, kimliği ve beyanları güvenli ve gizliliği koruyan bir şekilde kanıtlamanın güçlü bir yoludur. Peki kullanıcılar bu kimlik bilgilerini ilk etapta nasıl elde eder? İşte bu noktada Verici'nin (Issuer) rolü kritik hale gelir. Bir Verici, bir devlet kurumu, üniversite veya banka gibi güvenilir bir varlıktır ve kullanıcılara dijital olarak imzalanmış kimlik bilgileri oluşturmaktan ve dağıtmaktan sorumludur.
Bu kılavuz, bir Dijital Kimlik Bilgisi Vericisi oluşturmak için kapsamlı, adım adım bir rehber sunmaktadır. Kullanıcıların bir Verici'den nasıl kimlik bilgisi alabileceğini ve bunları dijital cüzdanlarında güvenli bir şekilde nasıl saklayabileceğini tanımlayan modern bir standart olan OpenID for Verifiable Credential Issuance (OpenID4VCI) protokolüne odaklanacağız.
Sonuçta, aşağıdaki işlemleri yapabilen fonksiyonel bir Next.js uygulaması ortaya çıkacak:
Recent Articles
📝
Dijital Kimlik Bilgisi Doğrulayıcı Nasıl Oluşturulur (Geliştirici Rehberi)
📝
Dijital Kimlik Bilgisi Vericisi Nasıl Oluşturulur (Geliştirici Kılavuzu)
📖
WebAuthn Resident Key: Passkey Olarak Keşfedilebilir Kimlik Bilgileri
🔑
Fiziksel Yaka Kartı Erişimi ve Passkey'ler: Teknik Rehber
🔑
MFA'yı Zorunlu Kılma ve Passkey'lere Geçiş: En İyi Uygulamalar
Devam etmeden önce, birbiriyle ilişkili ancak farklı iki kavram arasındaki ayrımı netleştirmek önemlidir:
Dijital Kimlik Bilgileri (Genel Terim): Bu, kimlik bilgilerinin, sertifikaların veya onayların her türlü dijital formunu kapsayan geniş bir kategoridir. Bunlar arasında basit dijital sertifikalar, temel dijital rozetler veya kriptografik güvenlik özelliklerine sahip olabilen ya da olmayan elektronik olarak saklanan herhangi bir kimlik bilgisi bulunabilir.
Doğrulanabilir Kimlik Bilgileri (VC'ler - W3C Standardı): Bu, W3C Doğrulanabilir Kimlik Bilgileri Veri Modeli standardını takip eden özel bir dijital kimlik bilgisi türüdür. Doğrulanabilir Kimlik Bilgileri, bağımsız olarak doğrulanabilen, kriptografik olarak imzalanmış, kurcalamaya karşı korumalı ve gizliliğe saygılı kimlik bilgileridir. Aşağıdaki gibi belirli teknik gereksinimleri içerirler:
Bu kılavuzda, özellikle W3C standardını takip eden bir Doğrulanabilir Kimlik Bilgisi vericisi oluşturuyoruz, sadece herhangi bir dijital kimlik bilgisi sistemi değil. Kullandığımız OpenID4VCI protokolü, özellikle Doğrulanabilir Kimlik Bilgileri düzenlemek için tasarlanmıştır ve uygulayacağımız JWT-VC formatı, Doğrulanabilir Kimlik Bilgileri için W3C uyumlu bir formattır.
Dijital kimlik bilgilerinin arkasındaki sihir, üç kilit oyuncuyu içeren basit ama güçlü bir "güven üçgeni" modelinde yatmaktadır:
Düzenleme akışı, bu ekosistemdeki ilk adımdır. Verici, kullanıcının bilgilerini doğrular ve onlara bir kimlik bilgisi sağlar. Sahip, bu kimlik bilgisini cüzdanına aldıktan sonra, kimliğini veya beyanlarını kanıtlamak için bir Doğrulayıcıya sunabilir ve böylece üçgen tamamlanır.
İşte son uygulamanın çalışır haldeki hızlı bir görünümü:
Adım 1: Kullanıcı Veri Girişi Kullanıcı, yeni bir kimlik bilgisi talep etmek için kişisel bilgileriyle bir form doldurur.
Adım 2: Kimlik Bilgisi Teklifi Oluşturma Uygulama, QR kodu ve önceden yetkilendirilmiş bir kod olarak görüntülenen güvenli bir kimlik bilgisi teklifi oluşturur.
Adım 3: Cüzdan Etkileşimi Kullanıcı, uyumlu bir cüzdanla (ör. Sphereon Wallet) QR kodunu tarar ve düzenlemeyi yetkilendirmek için bir PIN girer.
Adım 4: Kimlik Bilgisi Düzenlendi Cüzdan, yeni düzenlenen dijital kimlik bilgisini alır ve gelecekteki kullanım için saklar.
Koda dalmadan önce, ihtiyacınız olacak temel bilgi ve araçları ele alalım. Bu kılavuz, web geliştirme kavramlarına temel düzeyde aşina olduğunuzu varsayar, ancak aşağıdaki ön koşullar bir kimlik bilgisi vericisi oluşturmak için gereklidir.
Vericimiz, cüzdanlar ve düzenleme hizmetleri arasında birlikte çalışabilirliği sağlayan bir dizi açık standart üzerine kurulmuştur. Bu eğitim için aşağıdakilere odaklanacağız:
Standart / Protokol | Açıklama |
---|---|
OpenID4VCI | OpenID for Verifiable Credential Issuance. Kullanacağımız temel protokol budur. Bir kullanıcının (cüzdanı aracılığıyla) bir Verici'den nasıl kimlik bilgisi talep edip alabileceğine dair standart bir akış tanımlar. |
JWT-VC | JWT tabanlı Doğrulanabilir Kimlik Bilgileri. Düzenleyeceğimiz kimlik bilgisinin formatıdır. Doğrulanabilir kimlik bilgilerini JSON Web Token (JWT) olarak kodlayan bir W3C standardıdır, bu da onları kompakt ve web dostu yapar. |
ISO mDoc | ISO/IEC 18013-5. Mobil Sürücü Belgeleri (mDL'ler) için uluslararası standart. Bir JWT-VC düzenlesek de, içindeki beyanlar mDoc veri modeliyle uyumlu olacak şekilde yapılandırılmıştır (ör. eu.europa.ec.eudi.pid.1 ). |
OAuth 2.0 | OpenID4VCI tarafından kullanılan temel yetkilendirme çerçevesi. Güvenli ve kullanıcı dostu kimlik bilgisi düzenlemesi için tasarlanmış özel bir grant türü olan pre-authorized_code akışını uygulayacağız. |
OpenID4VCI, kimlik bilgileri düzenlemek için iki ana yetkilendirme akışını destekler:
Önceden Yetkilendirilmiş Kod Akışı (Pre-Authorized Code Flow): Bu akışta, Verici,
kullanıcıya anında sunulan kısa ömürlü, tek kullanımlık bir kod (pre-authorized_code
)
oluşturur. Kullanıcının cüzdanı daha sonra bu kodu
doğrudan bir kimlik bilgisiyle değiştirebilir. Bu akış, kullanıcının zaten kimliğinin
doğrulandığı ve Verici'nin web sitesinde bulunduğu senaryolar için
idealdir, çünkü yönlendirmeler olmadan sorunsuz, anında bir düzenleme deneyimi sağlar.
Yetkilendirme Kodu Akışı (Authorization Code Flow): Bu, kullanıcının onay vermek
için bir yetkilendirme sunucusuna yönlendirildiği standart
OAuth 2.0 akışıdır. Onaydan sonra, sunucu kayıtlı bir
redirect_uri
adresine bir authorization_code
gönderir. Bu akış, kullanıcı adına
düzenleme sürecini başlatan üçüncü taraf uygulamalar için daha uygundur.
Bu eğitim için pre-authorized_code
akışını kullanacağız. Bu yaklaşımı seçtik çünkü
daha basit ve belirli kullanım durumumuz için daha doğrudan bir kullanıcı deneyimi
sağlıyor: bir kullanıcının doğrudan Verici'nin kendi web sitesinden
bir kimlik bilgisi talep etmesi. Karmaşık yönlendirmelere ve istemci kaydına olan ihtiyacı
ortadan kaldırarak, temel düzenleme mantığını anlamayı ve uygulamayı kolaylaştırır.
Bu standartların birleşimi, çok çeşitli dijital cüzdanlarla uyumlu bir verici oluşturmamıza ve kullanıcı için güvenli, standartlaştırılmış bir süreç sağlamamıza olanak tanır.
Vericimizi oluşturmak için, doğrulayıcı için kullandığımız aynı sağlam ve modern teknoloji yığınını kullanacağız, böylece tutarlı ve yüksek kaliteli bir geliştirici deneyimi sağlayacağız.
Hem ön uç hem de arka uç kodumuz için TypeScript kullanacağız. Statik tiplemesi, bir verici gibi güvenliğin kritik olduğu bir uygulamada paha biçilmezdir, çünkü yaygın hataları önlemeye yardımcı olur ve kodun genel kalitesini ve sürdürülebilirliğini artırır.
Next.js, tam yığın uygulamalar oluşturmak için sorunsuz, entegre bir deneyim sağladığı için tercih ettiğimiz çerçevedir.
Uygulamamız, belirli görevleri yerine getirmek için birkaç anahtar kütüphaneye dayanacaktır:
pre-authorized_code
değerleri oluşturmak için kullanacağımız benzersiz
tanımlayıcılar üretmek için bir kütüphane.Vericinizi test etmek için, OpenID4VCI protokolünü destekleyen bir mobil cüzdana ihtiyacınız olacak. Bu eğitim için, hem Android hem de iOS için mevcut olan Sphereon Wallet'ı öneriyoruz.
Sphereon Wallet Nasıl Kurulur:
Bir kimlik bilgisi düzenlemek, güven ve özgünlüğü sağlamak için temel kriptografik kavramlara dayanan, güvenliğin kritik olduğu bir işlemdir.
Özünde, bir Doğrulanabilir Kimlik Bilgisi, Verici tarafından dijital olarak imzalanmış bir dizi beyandır. Bu imza iki güvence sağlar:
Dijital imzalar, açık/özel anahtar kriptografisi kullanılarak oluşturulur. İşte nasıl çalıştığı:
Uygulamamızda, bir Eliptik Eğri (EC) anahtar çifti oluşturacağız ve JWT-VC'yi imzalamak
için ES256
algoritmasını kullanacağız. Açık anahtar, Verici'nin DID'sine (did:web
)
gömülüdür, bu da herhangi bir Doğrulayıcının onu keşfetmesine ve kimlik bilgisinin
imzasını doğrulamasına olanak tanır. Not: JWT'lerimizde aud
(audience) beyanı
kasıtlı olarak çıkarılmıştır, çünkü kimlik bilgisi genel amaçlı olarak tasarlanmıştır ve
belirli bir cüzdana bağlı değildir. Kullanımı belirli bir kitleyle kısıtlamak
istiyorsanız, bir aud
beyanı ekleyin ve buna göre ayarlayın.
Verici uygulamamız, ön uç ve arka uç mantığı arasında net bir ayrım olan tam yığın bir
Next.js projesi olarak inşa edilmiştir. Bu mimari, sunucudaki tüm güvenlik açısından
kritik işlemleri ele alırken sorunsuz bir kullanıcı deneyimi yaratmamızı sağlar.
Önemli: SQL'de yer alan verification_sessions
ve verified_credentials
tabloları bu
verici için gerekli değildir, ancak bütünlük için dahil edilmiştir.
src/app/issue/page.tsx
): Kullanıcıların bir kimlik bilgisi talep etmek için
verilerini girmelerine olanak tanıyan tek bir React sayfası.
Düzenleme sürecini başlatmak için arka ucumuzdaki API'lere çağrılar yapar.src/app/api/issue/...
): OpenID4VCI protokolünü uygulayan bir
dizi sunucu tarafı uç nokta.
/.well-known/openid-credential-issuer
: Halka açık bir meta veri uç noktası. Bu,
bir cüzdanın, vericinin yetkilendirme sunucusu, token uç noktası, kimlik bilgisi uç
noktası ve sunduğu kimlik bilgisi türleri de dahil olmak üzere yeteneklerini
keşfetmek için kontrol edeceği ilk URL'dir./.well-known/openid-configuration
: Standart bir OpenID Connect keşif uç noktası.
Yukarıdakiyle yakından ilişkili olsa da, bu uç nokta daha geniş OIDC ile ilgili
yapılandırmayı sunar ve standart OpenID istemcileriyle birlikte çalışabilirlik için
genellikle gereklidir./.well-known/did.json
: Vericimiz için DID Belgesi. did:web
yöntemini
kullanırken, bu dosya vericinin açık anahtarlarını yayınlamak için kullanılır;
doğrulayıcılar bu anahtarları, düzenlediği kimlik bilgilerinin imzalarını doğrulamak
için kullanabilir.authorize/route.ts
: Bir pre-authorized_code
ve bir kimlik bilgisi teklifi
oluşturur.token/route.ts
: pre-authorized_code
'u bir
erişim tokenı ile değiştirir.credential/route.ts
: Son, kriptografik olarak imzalanmış JWT-VC'yi düzenler.schemas/pid/route.ts
: PID kimlik bilgisi için JSON şemasını sunar. Bu, kimlik
bilgisinin herhangi bir tüketicisinin yapısını ve veri türlerini anlamasını sağlar.src/lib/
):
database.ts
: Yetkilendirme kodları ve verici anahtarları gibi tüm veritabanı
etkileşimlerini yönetir.crypto.ts
: Anahtar oluşturma ve JWT imzalama dahil tüm kriptografik işlemleri
yönetir.İşte düzenleme akışını gösteren bir diyagram:
Standartlar, protokoller ve mimari hakkında sağlam bir anlayışa sahip olduğumuza göre, vericimizi oluşturmaya başlayabiliriz.
Birlikte Takip Edin veya Son Kodu Kullanın
Şimdi kurulum ve kod uygulamasını adım adım inceleyeceğiz. Doğrudan bitmiş ürüne geçmeyi tercih ederseniz, tam projeyi GitHub depomuzdan klonlayabilir ve yerel olarak çalıştırabilirsiniz.
git clone https://github.com/corbado/digital-credentials-example.git
İlk olarak, yeni bir Next.js projesi başlatacağız, gerekli bağımlılıkları yükleyeceğiz ve veritabanımızı başlatacağız.
Terminalinizi açın, projenizi oluşturmak istediğiniz dizine gidin ve aşağıdaki komutu çalıştırın. Bu proje için App Router, TypeScript ve Tailwind CSS kullanıyoruz.
npx create-next-app@latest . --ts --eslint --tailwind --app --src-dir --import-alias "@/*" --use-npm
Bu komut, mevcut dizininizde yeni bir Next.js uygulaması oluşturur.
Sıradaki adım, JWT'leri, veritabanı bağlantılarını ve UUID üretimini yönetecek kütüphaneleri yüklemek.
npm install jose mysql2 uuid @types/uuid
Bu komut şunları yükler:
jose
: JSON Web Token'larını (JWT) imzalamak ve doğrulamak için.mysql2
: Veritabanımız için MySQL istemcisi.uuid
: Benzersiz challenge dizeleri oluşturmak için.@types/uuid
: uuid
kütüphanesi için TypeScript tipleri.Arka ucumuz, yetkilendirme kodlarını, düzenleme oturumlarını ve verici anahtarlarını
saklamak için bir MySQL veritabanı gerektirir.
Bunu kolaylaştırmak için bir docker-compose.yml
dosyası ekledik.
Depoyu klonladıysanız, sadece docker-compose up -d
komutunu çalıştırabilirsiniz.
Sıfırdan oluşturuyorsanız, aşağıdaki içeriğe sahip docker-compose.yml
adında bir dosya
oluşturun:
services: mysql: image: mysql:8.0 restart: always environment: MYSQL_ROOT_PASSWORD: rootpassword MYSQL_DATABASE: digital_credentials MYSQL_USER: app_user MYSQL_PASSWORD: app_password ports: - "3306:3306" volumes: - mysql_data:/var/lib/mysql - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] timeout: 20s retries: 10 volumes: mysql_data:
Bu Docker Compose kurulumu ayrıca bir SQL başlatma betiği gerektirir. sql
adında bir
dizin oluşturun ve içinde, hem doğrulayıcı hem de verici için gerekli tabloları ayarlamak
üzere aşağıdaki içeriğe sahip init.sql
adında bir dosya oluşturun:
-- Create database if not exists CREATE DATABASE IF NOT EXISTS digital_credentials; USE digital_credentials; -- Table for storing challenges CREATE TABLE IF NOT EXISTS challenges ( id VARCHAR(36) PRIMARY KEY, challenge VARCHAR(255) NOT NULL UNIQUE, expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, used BOOLEAN DEFAULT FALSE, INDEX idx_challenge (challenge), INDEX idx_expires_at (expires_at) ); -- Table for storing verification sessions CREATE TABLE IF NOT EXISTS verification_sessions ( id VARCHAR(36) PRIMARY KEY, challenge_id VARCHAR(36), status ENUM('pending', 'verified', 'failed', 'expired') DEFAULT 'pending', presentation_data JSON, verified_at TIMESTAMP NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (challenge_id) REFERENCES challenges(id) ON DELETE CASCADE, INDEX idx_challenge_id (challenge_id), INDEX idx_status (status) ); -- Table for storing verified credentials data (optional) CREATE TABLE IF NOT EXISTS verified_credentials ( id VARCHAR(36) PRIMARY KEY, session_id VARCHAR(36), credential_type VARCHAR(255), issuer VARCHAR(255), subject VARCHAR(255), claims JSON, verified_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (session_id) REFERENCES verification_sessions(id) ON DELETE CASCADE, INDEX idx_session_id (session_id), INDEX idx_credential_type (credential_type) ); -- ISSUER TABLES -- Table for storing authorization codes in OpenID4VCI flow CREATE TABLE IF NOT EXISTS authorization_codes ( id VARCHAR(36) PRIMARY KEY, code VARCHAR(255) NOT NULL UNIQUE, client_id VARCHAR(255), scope VARCHAR(255), code_challenge VARCHAR(255), code_challenge_method VARCHAR(50), redirect_uri TEXT, user_pin VARCHAR(10), expires_at TIMESTAMP NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, used BOOLEAN DEFAULT FALSE, INDEX idx_code (code), INDEX idx_expires_at (expires_at) ); -- Table for storing issuance sessions CREATE TABLE IF NOT EXISTS issuance_sessions ( id VARCHAR(36) PRIMARY KEY, authorization_code_id VARCHAR(36), access_token VARCHAR(255), token_type VARCHAR(50) DEFAULT 'Bearer', expires_in INT DEFAULT 3600, c_nonce VARCHAR(255), c_nonce_expires_at TIMESTAMP, status ENUM('pending', 'authorized', 'credential_issued', 'expired', 'failed') DEFAULT 'pending', user_data JSON, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (authorization_code_id) REFERENCES authorization_codes(id) ON DELETE CASCADE, INDEX idx_access_token (access_token), INDEX idx_c_nonce (c_nonce), INDEX idx_status (status) ); -- Table for storing issued credentials CREATE TABLE IF NOT EXISTS issued_credentials ( id VARCHAR(36) PRIMARY KEY, session_id VARCHAR(36), credential_id VARCHAR(255), credential_type VARCHAR(255) DEFAULT 'jwt_vc', doctype VARCHAR(255) DEFAULT 'eu.europa.ec.eudi.pid.1', credential_data LONGTEXT, -- Base64 encoded mDoc credential_claims JSON, issuer_did VARCHAR(255), subject_id VARCHAR(255), issued_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP, revoked BOOLEAN DEFAULT FALSE, revoked_at TIMESTAMP NULL, FOREIGN KEY (session_id) REFERENCES issuance_sessions(id) ON DELETE CASCADE, INDEX idx_credential_id (credential_id), INDEX idx_session_id (session_id), INDEX idx_doctype (doctype), INDEX idx_subject_id (subject_id), INDEX idx_issued_at (issued_at) ); -- Table for storing issuer keys (simplified for demo) CREATE TABLE IF NOT EXISTS issuer_keys ( id VARCHAR(36) PRIMARY KEY, key_id VARCHAR(255) NOT NULL UNIQUE, key_type VARCHAR(50) NOT NULL, -- 'EC', 'RSA' algorithm VARCHAR(50) NOT NULL, -- 'ES256', 'RS256', etc. public_key TEXT NOT NULL, -- JWK format private_key TEXT NOT NULL, -- JWK format (encrypted in production) is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX idx_key_id (key_id), INDEX idx_is_active (is_active) );
Her iki dosya da yerinde olduğunda, terminalinizi proje kök dizininde açın ve çalıştırın:
docker-compose up -d
Bu komut, uygulamamızın kullanıma hazır olması için arka planda bir MySQL container'ı başlatacaktır.
API uç noktalarını oluşturmadan önce, temel iş mantığını yönetecek paylaşılan kütüphaneleri oluşturalım. Bu yaklaşım, API rotalarımızı temiz ve HTTP isteklerini işlemeye odaklı tutarken, karmaşık işleri bu modüllere devreder.
src/lib/database.ts
)#Bu dosya, tüm veritabanı etkileşimleri için tek doğruluk kaynağıdır. MySQL container'ımıza
bağlanmak için mysql2
kütüphanesini kullanır ve tablolarımızdaki kayıtları oluşturmak,
okumak ve güncellemek için bir dizi dışa aktarılmış fonksiyon sağlar. Bu soyutlama
katmanı, kodumuzu daha modüler ve bakımı daha kolay hale getirir.
src/lib/database.ts
dosyasını aşağıdaki içerikle oluşturun:
// src/lib/database.ts import mysql from "mysql2/promise"; // Database connection configuration const dbConfig = { host: process.env.DATABASE_HOST || "localhost", port: parseInt(process.env.DATABASE_PORT || "3306"), user: process.env.DATABASE_USER || "app_user", password: process.env.DATABASE_PASSWORD || "app_password", database: process.env.DATABASE_NAME || "digital_credentials", timezone: "+00:00", }; let connection: mysql.Connection | null = null; export async function getConnection(): Promise<mysql.Connection> { if (!connection) { connection = await mysql.createConnection(dbConfig); } return connection; } // Data-Access-Object (DAO) functions for each table // ... (e.g., createChallenge, getChallenge, createAuthorizationCode, etc.)
Not: Kısalık olması açısından, DAO fonksiyonlarının tam listesi çıkarılmıştır. Tam kodu proje deposunda bulabilirsiniz. Bu dosya, challenge'ları, doğrulama oturumlarını, yetkilendirme kodlarını, düzenleme oturumlarını ve verici anahtarlarını yönetmek için fonksiyonlar içerir.
src/lib/crypto.ts
)#Bu dosya, güvenlik açısından kritik tüm kriptografik işlemleri yönetir. Anahtar çiftleri
oluşturmak ve JSON Web Token'larını (JWT) imzalamak için jose
kütüphanesini kullanır.
Anahtar Oluşturma generateIssuerKeyPair
fonksiyonu, kimlik bilgilerini imzalamak
için kullanılacak yeni bir Eliptik Eğri anahtar çifti oluşturur. Açık anahtar, did.json
belgemizde yayınlanabilmesi için JSON Web Key (JWK) formatında dışa aktarılır.
// src/lib/crypto.ts import { generateKeyPair, exportJWK, SignJWT } from "jose"; export async function generateIssuerKeyPair(keyId: string, issuerDid: string) { const { publicKey, privateKey } = await generateKeyPair("ES256", { crv: "P-256", extractable: true, }); const publicKeyJWK = await exportJWK(publicKey); publicKeyJWK.kid = keyId; // Assign a unique key ID // ... (private key export and other setup) return { publicKey, privateKey, publicKeyJWK /* ... */ }; }
JWT Kimlik Bilgisi Oluşturma createJWTVerifiableCredential
fonksiyonu, düzenleme
sürecinin çekirdeğidir. Kullanıcının beyanlarını, vericinin anahtar çiftini ve diğer meta
verileri alır ve bunları imzalı bir JWT-VC oluşturmak için kullanır.
// src/lib/crypto.ts export async function createJWTVerifiableCredential( claims: MDocClaims, issuerKeyPair: IssuerKeyPair, subjectId: string, audience: string, ): Promise<string> { const now = Math.floor(Date.now() / 1000); const oneYear = 365 * 24 * 60 * 60; const vcPayload = { // The issuer's DID iss: issuerKeyPair.issuerDid, // The subject's (holder's) DID sub: subjectId, // The time the credential was issued (iat) and when it expires (exp) iat: now, exp: now + oneYear, // The Verifiable Credential data model vc: { "@context": [ "https://www.w3.org/2018/credentials/v1", "https://europa.eu/eudi/pid/v1", ], type: ["VerifiableCredential", "eu.europa.ec.eudi.pid.1"], issuer: issuerKeyPair.issuerDid, issuanceDate: new Date(now * 1000).toISOString(), credentialSubject: { id: subjectId, ...claims, }, }, }; // Sign the payload with the issuer's private key return await new SignJWT(vcPayload) .setProtectedHeader({ alg: issuerKeyPair.algorithm, kid: issuerKeyPair.keyId, typ: "JWT", }) .sign(issuerKeyPair.privateKey); }
Bu fonksiyon, JWT yükünü W3C Doğrulanabilir Kimlik Bilgileri Veri Modeli'ne göre oluşturur ve vericinin özel anahtarıyla imzalayarak güvenli ve doğrulanabilir bir kimlik bilgisi üretir.
Next.js uygulamamız, aynı projenin parçası olmalarına rağmen ön uç ve arka uç arasındaki endişeleri ayırmak için yapılandırılmıştır. Bu, hem UI sayfaları hem de API uç noktaları için App Router'dan yararlanılarak elde edilir.
Ön Uç (src/app/issue/page.tsx
): /issue
rotası için kullanıcı arayüzünü
tanımlayan tek bir React sayfa bileşeni. Kullanıcı girişini
yönetir ve arka uç API'mizle iletişim kurar.
Arka Uç API Rotaları (src/app/api/...
):
.well-known/.../route.ts
): Bu rotalar, cüzdanların ve diğer
istemcilerin vericinin yeteneklerini ve açık anahtarlarını keşfetmesine olanak
tanıyan halka açık meta veri uç noktalarını sunar.issue/.../route.ts
): Bu uç noktalar, kimlik bilgisi teklifleri
oluşturma, token düzenleme ve son kimlik bilgisini imzalama dahil olmak üzere temel
OpenID4VCI mantığını uygular.schemas/pid/route.ts
): Bu rota, kimlik bilgisinin yapısını tanımlayan
JSON şemasını sunar.Kütüphane (src/lib/
): Bu dizin, arka uçta paylaşılan yeniden kullanılabilir
mantığı içerir.
database.ts
: SQL sorgularını soyutlayarak tüm veritabanı etkileşimlerini yönetir.crypto.ts
: Anahtar oluşturma ve JWT imzalama gibi tüm kriptografik işlemleri
yönetir.Bu net ayrım, uygulamayı modüler ve bakımı daha kolay hale getirir.
Not: generateIssuerDid()
fonksiyonu, verici alan adınızla eşleşen geçerli bir
did:web
döndürmelidir. Dağıtıldığında, .well-known/did.json
dosyasının,
doğrulayıcıların kimlik bilgilerini doğrulayabilmesi için o alan adında HTTPS üzerinden
sunulması gerekir.
Ön ucumuz, kullanıcıların yeni bir dijital kimlik bilgisi talep etmeleri için basit bir form sağlayan tek bir React sayfasıdır. Sorumlulukları şunlardır:
Temel mantık, kullanıcı formu gönderdiğinde tetiklenen handleSubmit
fonksiyonunda ele
alınır.
// src/app/issue/page.tsx const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); setLoading(true); setError(null); setCredentialOffer(null); try { // 1. Validate required fields if (!userData.given_name || !userData.family_name || !userData.birth_date) { throw new Error("Please fill in all required fields"); } // 2. Request a credential offer from the backend const response = await fetch("/api/issue/authorize", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ user_data: userData, }), }); if (!response.ok) { const errorData = await response.json(); throw new Error( errorData.error_description || "Failed to create credential offer", ); } // 3. Set the credential offer in state to display the QR code const result = await response.json(); setCredentialOffer(result); } catch (err) { const errorMessage = (err as Error).message || "Unknown error occurred"; setError(errorMessage); } finally { setLoading(false); } };
Bu fonksiyon üç temel eylem gerçekleştirir:
/api/issue/authorize
uç noktamıza bir POST
isteği gönderir.Dosyanın geri kalanı, formu ve QR kodu ekranını oluşturmak için standart React kodu içerir. Tam dosyayı proje deposunda görüntüleyebilirsiniz.
Arka uç API'sini oluşturmadan önce, ortamımızı yapılandırmamız ve keşif uç noktalarını
ayarlamamız gerekir. Bu .well-known
dosyaları, cüzdanların vericimizi bulması ve onunla
nasıl etkileşim kuracağını anlaması için çok önemlidir.
Projenizin kök dizininde .env.local
adında bir dosya oluşturun ve aşağıdaki satırı
ekleyin. Bu URL, bir mobil cüzdanın ona ulaşabilmesi için halka açık olmalıdır. Yerel
geliştirme için, localhost
'unuzu dışarıya açmak için
ngrok gibi bir tünel hizmeti
kullanabilirsiniz.
NEXT_PUBLIC_BASE_URL=http://localhost:3000
Cüzdanlar, bir vericinin yeteneklerini standart .well-known
URL'lerini sorgulayarak
keşfeder. Bu uç noktalardan üç tane oluşturmamız gerekiyor.
1. Verici Meta Verileri (/.well-known/openid-credential-issuer
)
Bu, OpenID4VCI için birincil keşif dosyasıdır. Cüzdana, uç noktaları, sunduğu kimlik bilgisi türleri ve desteklenen kriptografik algoritmalar dahil olmak üzere verici hakkında bilmesi gereken her şeyi anlatır.
src/app/.well-known/openid-credential-issuer/route.ts
dosyasını oluşturun:
// src/app/.well-known/openid-credential-issuer/route.ts import { NextResponse } from "next/server"; export async function GET() { const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"; const issuerMetadata = { // The issuer's unique identifier. issuer: baseUrl, // The URL of the authorization server. For simplicity, our issuer is its own authorization server. authorization_servers: [baseUrl], // The URL of the credential issuer. credential_issuer: baseUrl, // The endpoint where the wallet will POST to receive the actual credential. credential_endpoint: `${baseUrl}/api/issue/credential`, // The endpoint where the wallet exchanges an authorization code for an access token. token_endpoint: `${baseUrl}/api/issue/token`, // The endpoint for the authorization flow (not used in our pre-authorized flow, but good practice to include). authorization_endpoint: `${baseUrl}/api/issue/authorize`, // Indicates support for the pre-authorized code flow without requiring client authentication. pre_authorized_grant_anonymous_access_supported: true, // Human-readable information about the issuer. display: [ { name: "Corbado Credentials Issuer", locale: "en-US", }, ], // A list of the credential types this issuer can issue. credential_configurations_supported: { "eu.europa.ec.eudi.pid.1": { // The format of the credential (e.g., jwt_vc, mso_mdoc). format: "jwt_vc", // The specific document type, conforming to ISO mDoc standards. doctype: "eu.europa.ec.eudi.pid.1", // The OAuth 2.0 scope associated with this credential type. scope: "eu.europa.ec.eudi.pid.1", // Methods the wallet can use to prove possession of its key. cryptographic_binding_methods_supported: ["jwk"], // Signing algorithms the issuer supports for this credential. credential_signing_alg_values_supported: ["ES256"], // Proof-of-possession types the wallet can use. proof_types_supported: { jwt: { proof_signing_alg_values_supported: ["ES256", "ES384", "ES512"], }, }, // Display properties for the credential. display: [ { name: "Corbado Credential Issuer", locale: "en-US", logo: { uri: `${baseUrl}/logo.png`, alt_text: "EU Digital Identity", }, background_color: "#003399", text_color: "#FFFFFF", }, ], // A list of the claims (attributes) in the credential. claims: { "eu.europa.ec.eudi.pid.1": { given_name: { mandatory: true, display: [{ name: "Given Name", locale: "en-US" }], }, family_name: { mandatory: true, display: [{ name: "Family Name", locale: "en-US" }], }, birth_date: { mandatory: true, display: [{ name: "Date of Birth", locale: "en-US" }], }, }, }, }, }, // Authentication methods supported by the token endpoint. 'none' means public client. token_endpoint_auth_methods_supported: ["none"], // PKCE code challenge methods supported. code_challenge_methods_supported: ["S256"], // OAuth 2.0 grant types the issuer supports. grant_types_supported: [ "authorization_code", "urn:ietf:params:oauth:grant-type:pre-authorized_code", ], }; return NextResponse.json(issuerMetadata, { headers: { "Content-Type": "application/json", "Cache-Control": "no-cache, no-store, must-revalidate", Pragma: "no-cache", Expires: "0", }, }); }
2. OpenID Yapılandırması (/.well-known/openid-configuration
)
Bu, daha geniş bir yapılandırma ayrıntıları seti sağlayan standart bir OIDC keşif belgesidir.
src/app/.well-known/openid-configuration/route.ts
dosyasını oluşturun:
// src/app/.well-known/openid-configuration/route.ts import { NextResponse } from "next/server"; export async function GET() { const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"; const openidConfiguration = { // The issuer's unique identifier. credential_issuer: baseUrl, // The endpoint where the wallet will POST to receive the actual credential. credential_endpoint: `${baseUrl}/api/issue/credential`, // The endpoint for the authorization flow. authorization_endpoint: `${baseUrl}/api/issue/authorize`, // The endpoint where the wallet exchanges an authorization code for an access token. token_endpoint: `${baseUrl}/api/issue/token`, // A list of the credential types this issuer can issue. credential_configurations_supported: { "eu.europa.ec.eudi.pid.1": { format: "jwt_vc", scope: "eu.europa.ec.eudi.pid.1", cryptographic_binding_methods_supported: ["jwk"], credential_signing_alg_values_supported: ["ES256", "ES384", "ES512"], proof_types_supported: { jwt: { proof_signing_alg_values_supported: ["ES256", "ES384", "ES512"], }, }, }, }, // OAuth 2.0 grant types the issuer supports. grant_types_supported: [ "authorization_code", "urn:ietf:params:oauth:grant-type:pre-authorized_code", ], // Indicates support for the pre-authorized code flow. pre_authorized_grant_anonymous_access_supported: true, // PKCE code challenge methods supported. code_challenge_methods_supported: ["S256"], // Authentication methods supported by the token endpoint. token_endpoint_auth_methods_supported: ["none"], // OAuth 2.0 scopes the issuer supports. scopes_supported: ["eu.europa.ec.eudi.pid.1"], }; return NextResponse.json(openidConfiguration, { headers: { "Content-Type": "application/json", "Cache-Control": "no-cache, no-store, must-revalidate", Pragma: "no-cache", Expires: "0", }, }); }
3. DID Belgesi (/.well-known/did.json
)
Bu dosya, did:web
yöntemini kullanarak vericinin açık anahtarını yayınlar ve herkesin
onun tarafından düzenlenen kimlik bilgilerinin imzasını doğrulamasını sağlar.
src/app/.well-known/did.json/route.ts
dosyasını oluşturun:
// src/app/.well-known/did.json/route.ts import { NextResponse } from "next/server"; import { getActiveIssuerKey } from "../../../lib/database"; import { generateIssuerDid } from "../../../lib/crypto"; export async function GET() { const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"; const issuerKey = await getActiveIssuerKey(); if (!issuerKey) { return NextResponse.json( { error: "No active issuer key found" }, { status: 404 }, ); } const publicKeyJWK = JSON.parse(issuerKey.public_key); const didId = generateIssuerDid(); const didDocument = { // The context defines the vocabulary used in the document. "@context": [ "https://www.w3.org/ns/did/v1", "https://w3id.org/security/suites/jws-2020/v1", ], // The DID URI, which is the unique identifier for the issuer. id: didId, // The DID controller, which is the entity that controls the DID. Here, it's the issuer itself. controller: didId, // A list of public keys that can be used to verify signatures from the issuer. verificationMethod: [ { // A unique identifier for the key, scoped to the DID. id: `${didId}#${issuerKey.key_id}`, // The type of the key. type: "JsonWebKey2020", // The DID of the key's controller. controller: didId, // The public key in JWK format. publicKeyJwk: publicKeyJWK, }, ], // Specifies which keys can be used for authentication (proving control of the DID). authentication: [`${didId}#${issuerKey.key_id}`], // Specifies which keys can be used for creating verifiable credentials. assertionMethod: [`${didId}#${issuerKey.key_id}`], // A list of services provided by the DID subject, such as the issuer endpoint. service: [ { id: `${didId}#openid-credential-issuer`, type: "OpenIDCredentialIssuer", serviceEndpoint: `${baseUrl}/.well-known/openid-credential-issuer`, }, ], }; return NextResponse.json(didDocument, { headers: { "Content-Type": "application/did+json", "Cache-Control": "no-cache, no-store, must-revalidate", Pragma: "no-cache", Expires: "0", }, }); }
Neden Önbellekleme Yok? Bu üç uç noktanın da önbelleklemeyi agresif bir şekilde
önleyen başlıklar (Cache-Control: no-cache
, Pragma: no-cache
, Expires: 0
)
döndürdüğünü fark edeceksiniz. Bu, keşif belgeleri için kritik bir güvenlik
uygulamasıdır. Verici yapılandırmaları değişebilir - örneğin, bir kriptografik anahtar
döndürülebilir. Bir cüzdan veya istemci did.json
veya openid-credential-issuer
dosyasının eski bir sürümünü önbelleğe alsaydı, yeni kimlik bilgilerini doğrulayamaz
veya güncellenmiş uç noktalarla etkileşime giremezdi. İstemcileri her istekte yeni bir
kopya almaya zorlayarak, her zaman en güncel bilgilere sahip olmalarını sağlarız.
Halka açık altyapımızın son parçası kimlik bilgisi şeması uç noktasıdır. Bu rota, düzenlediğimiz PID kimlik bilgisinin yapısını, veri türlerini ve kısıtlamalarını resmi olarak tanımlayan bir JSON Şeması sunar. Cüzdanlar ve doğrulayıcılar, kimlik bilgisinin içeriğini doğrulamak için bu şemayı kullanabilir.
src/app/api/schemas/pid/route.ts
dosyasını aşağıdaki içerikle oluşturun:
// src/app/api/schemas/pid/route.ts import { NextResponse } from "next/server"; export async function GET() { const schema = { $schema: "https://json-schema.org/draft/2020-12/schema", $id: "https://example.com/schemas/pid", // Replace with your actual domain title: "PID Credential", description: "A schema for a Verifiable Credential representing a Personal Identification Document (PID).", type: "object", properties: { credentialSubject: { type: "object", properties: { given_name: { type: "string" }, family_name: { type: "string" }, birth_date: { type: "string", format: "date" }, // ... other properties of the credential subject }, required: ["given_name", "family_name", "birth_date"], }, // ... other top-level properties of a Verifiable Credential }, }; return NextResponse.json(schema, { headers: { "Content-Type": "application/schema+json", "Access-Control-Allow-Origin": "*", // Allow cross-origin requests }, }); }
Not: Bir PID kimlik bilgisi için JSON Şeması oldukça büyük ve ayrıntılı olabilir. Kısalık olması açısından, tam şema kısaltılmıştır. Tam dosyayı proje deposunda bulabilirsiniz.
Ön uç yerinde olduğuna göre, şimdi OpenID4VCI akışını yönetmek için sunucu tarafı
mantığına ihtiyacımız var. Ön ucun çağırdığı ilk uç nokta ile başlayacağız:
/api/issue/authorize
.
/api/issue/authorize
: Kimlik Bilgisi Teklifini Oluşturma#Bu uç nokta, kullanıcının verilerini almaktan, güvenli, tek kullanımlık bir kod
oluşturmaktan ve kullanıcının cüzdanının anlayabileceği bir credential_offer
oluşturmaktan sorumludur.
İşte temel mantık:
// src/app/api/issue/authorize/route.ts import { NextRequest, NextResponse } from "next/server"; import { v4 as uuidv4 } from "uuid"; import { createAuthorizationCode } from "@/lib/database"; export async function POST(request: NextRequest) { try { const body = await request.json(); const { user_data } = body; // 1. Validate user data if ( !user_data || !user_data.given_name || !user_data.family_name || !user_data.birth_date ) { return NextResponse.json({ error: "missing_user_data" }, { status: 400 }); } // 2. Generate a pre-authorized code and a PIN const code = uuidv4(); const expiresAt = new Date(Date.now() + 10 * 60 * 1000); // 10 minutes const txCode = Math.floor(1000 + Math.random() * 9000).toString(); // 4-digit PIN // 3. Store the code and user data await createAuthorizationCode(uuidv4(), code, expiresAt); // Note: This uses an in-memory store for demo purposes only. // In production, persist data securely in a database with proper expiry. if (!(global as any).userDataStore) (global as any).userDataStore = new Map(); (global as any).userDataStore.set(code, user_data); if (!(global as any).txCodeStore) (global as any).txCodeStore = new Map(); (global as any).txCodeStore.set(code, txCode); // 4. Create the credential offer object const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"; const credentialOffer = { // The issuer's identifier, which is its base URL. credential_issuer: baseUrl, // An array of credential types the issuer is offering. credential_configuration_ids: ["eu.europa.ec.eudi.pid.1"], // Specifies the grant types the wallet can use. grants: { // We are using the pre-authorized code flow. "urn:ietf:params:oauth:grant-type:pre-authorized_code": { // The one-time code the wallet will exchange for a token. "pre-authorized_code": code, // Indicates that the user must enter a PIN (tx_code) to redeem the code. user_pin_required: true, }, }, }; // 5. Create the full credential offer URI (a deep link for wallets) const credentialOfferUri = `openid-credential-offer://?credential_offer=${encodeURIComponent( JSON.stringify(credentialOffer), )}`; // The final response to the frontend. return NextResponse.json({ // The deep link for the QR code. credential_offer_uri: credentialOfferUri, // The raw pre-authorized code, for display or manual entry. pre_authorized_code: code, // The 4-digit PIN the user must enter in their wallet. tx_code: txCode, }); } catch (error) { console.error("Authorization error:", error); return NextResponse.json({ error: "server_error" }, { status: 500 }); } }
Bu uç noktadaki temel adımlar:
pre-authorized_code
(bir UUID) ve ek bir
güvenlik katmanı için 4 basamaklı bir tx_code
(PIN) oluşturur.pre-authorized_code
, kısa bir son kullanma süresiyle
veritabanında saklanır. Kullanıcının verileri ve PIN, bellekte koda bağlı olarak
saklanır.credential_offer
nesnesini
oluşturur. Bu nesne, cüzdana vericinin nerede olduğunu, hangi kimlik bilgilerini
sunduğunu ve bunları almak için gereken kodu söyler.openid-credential-offer://...
) oluşturur ve kullanıcıya göstermek için tx_code
ile
birlikte ön uca döndürür./api/issue/token
: Kodu Token ile Değiştirme#Kullanıcı QR kodunu tarayıp PIN'ini girdikten sonra, cüzdan bu uç noktaya bir POST
isteği gönderir. Görevi, pre-authorized_code
ve user_pin
(PIN) kodunu doğrulamak ve
geçerliyse, kısa ömürlü bir erişim tokenı düzenlemektir.
src/app/api/issue/token/route.ts
dosyasını aşağıdaki içerikle oluşturun:
// src/app/api/issue/token/route.ts import { NextRequest, NextResponse } from "next/server"; import { v4 as uuidv4 } from "uuid"; import { getAuthorizationCode, markAuthorizationCodeAsUsed, createIssuanceSession, } from "@/lib/database"; export async function POST(request: NextRequest) { try { const formData = await request.formData(); const grant_type = formData.get("grant_type") as string; const code = formData.get("pre-authorized_code") as string; const user_pin = formData.get("user_pin") as string; // 1. Validate the grant type if (grant_type !== "urn:ietf:params:oauth:grant-type:pre-authorized_code") { return NextResponse.json( { error: "unsupported_grant_type" }, { status: 400 }, ); } // 2. Validate the pre-authorized code const authCode = await getAuthorizationCode(code); if (!authCode) { return NextResponse.json( { error: "invalid_grant", error_description: "Invalid or expired code", }, { status: 400 }, ); } // 3. Validate the PIN (tx_code) const expectedTxCode = (global as any).txCodeStore?.get(code); if (expectedTxCode !== user_pin) { return NextResponse.json( { error: "invalid_grant", error_description: "Invalid PIN" }, { status: 400 }, ); } // 4. Generate access token and c_nonce const accessToken = uuidv4(); const cNonce = uuidv4(); const cNonceExpiresAt = new Date(Date.now() + 5 * 60 * 1000); // 5 minutes // 5. Create a new issuance session const userData = (global as any).userDataStore?.get(code); await createIssuanceSession( uuidv4(), authCode.id, accessToken, cNonce, cNonceExpiresAt, userData, ); // 6. Mark the code as used and clean up temporary data await markAuthorizationCodeAsUsed(code); (global as any).txCodeStore?.delete(code); (global as any).userDataStore?.delete(code); // 7. Return the access token response return NextResponse.json({ access_token: accessToken, token_type: "Bearer", expires_in: 3600, // 1 hour c_nonce: cNonce, c_nonce_expires_in: 300, // 5 minutes }); } catch (error) { console.error("Token endpoint error:", error); return NextResponse.json({ error: "server_error" }, { status: 500 }); } }
Bu uç noktadaki temel adımlar:
pre-authorized_code
grant türünü
kullandığından emin olur.pre-authorized_code
'un veritabanında mevcut olduğunu, süresinin
dolmadığını ve daha önce kullanılmadığını kontrol eder.user_pin
'i daha önce sakladığımız tx_code
ile karşılaştırır.access_token
ve kimlik bilgisi uç noktasına
yönelik tekrar saldırılarını önlemek için tek kullanımlık bir değer olan c_nonce
(credential nonce) oluşturur.issuance_sessions
kaydı
oluşturur.pre-authorized_code
'u kullanıldı olarak işaretler.access_token
ve c_nonce
'ı cüzdana döndürür./api/issue/credential
: İmzalı Kimlik Bilgisini Düzenleme#Bu, son ve en önemli uç noktadır. Cüzdan, /token
uç noktasından aldığı erişim tokenını
kullanarak bu rotaya kimliği doğrulanmış bir POST
isteği yapar. Bu uç noktanın görevi,
son doğrulamayı yapmak, kriptografik olarak imzalanmış kimlik bilgisini oluşturmak ve
cüzdana geri döndürmektir.
src/app/api/issue/credential/route.ts
dosyasını aşağıdaki içerikle oluşturun:
// src/app/api/issue/credential/route.ts import { NextRequest, NextResponse } from "next/server"; import { v4 as uuidv4 } from "uuid"; import { getIssuanceSessionByToken, updateIssuanceSession, createIssuedCredential, getActiveIssuerKey, } from "@/lib/database"; import { createJWTVerifiableCredential, importIssuerKeyPair, generateIssuerDid, } from "@/lib/crypto"; export async function POST(request: NextRequest) { try { // 1. Validate the Bearer token const authHeader = request.headers.get("authorization"); const accessToken = authHeader?.substring(7); const session = await getIssuanceSessionByToken(accessToken); if (!session) { return NextResponse.json({ error: "invalid_token" }, { status: 401 }); } // 2. Get the user data from the session const userData = session.user_data; if (!userData) { return NextResponse.json({ error: "missing_user_data" }, { status: 400 }); } // 3. Get the active issuer key const issuerKey = await getActiveIssuerKey(); if (!issuerKey) { // In a real application, you would have a more robust key management system. // For this demo, we can generate a key on the fly if one doesn't exist. // This part is omitted for brevity but is in the repository. return NextResponse.json( { error: "server_error", error_description: "Failed to get issuer key", }, { status: 500 }, ); } // 4. Create the JWT-VC const issuerDid = generateIssuerDid(); const keyPair = await importIssuerKeyPair( issuerKey.key_id, issuerKey.public_key, issuerKey.private_key, issuerDid, ); const subjectId = `did:example:${uuidv4()}`; const credentialData = await createJWTVerifiableCredential( userData, keyPair, subjectId, process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000", ); // 5. Store the issued credential in the database await createIssuedCredential(/* ... credential details ... */); await updateIssuanceSession(session.id, "credential_issued"); // 6. Return the signed credential return NextResponse.json({ format: "jwt_vc", credential: credentialData, c_nonce: uuidv4(), // A new nonce for subsequent requests c_nonce_expires_in: 300, }); } catch (error) { console.error("Credential endpoint error:", error); return NextResponse.json({ error: "server_error" }, { status: 500 }); } }
Bu uç noktadaki temel adımlar:
Authorization
başlığında geçerli bir Bearer
tokenı olup
olmadığını kontrol eder ve aktif düzenleme oturumunu bulmak için kullanır.src/lib/crypto.ts
dosyasındaki createJWTVerifiableCredential
yardımcımızı çağırır.Artık bir dijital kimlik bilgisi vericisinin eksiksiz, uçtan uca bir uygulamasına sahipsiniz. İşte onu yerel olarak nasıl çalıştıracağınız ve bir kavram kanıtından üretime hazır bir uygulamaya taşımak için neleri göz önünde bulundurmanız gerektiği.
Depoyu Klonlayın:
git clone https://github.com/corbado/digital-credentials-example.git cd digital-credentials-example
Bağımlılıkları Yükleyin:
npm install
Veritabanını Başlatın: Docker'ın çalıştığından emin olun, ardından MySQL container'ını başlatın:
docker-compose up -d
Ortamı Yapılandırın ve Tüneli Çalıştırın: Bu, yerel test için en kritik adımdır.
Mobil cüzdanınızın geliştirme makinenize internet üzerinden bağlanması gerektiğinden,
yerel sunucunuzu halka açık bir HTTPS URL'si ile açığa çıkarmalısınız. Bunun için
ngrok
kullanacağız.
a. ngrok'u başlatın:
ngrok http 3000
b. ngrok çıktısından HTTPS
URL'sini kopyalayın (ör. https://random-string.ngrok.io
).
c. .env.local
dosyası oluşturun ve URL'yi ayarlayın:
NEXT_PUBLIC_BASE_URL=https://<your-ngrok-url>
Uygulamayı Çalıştırın:
npm run dev
Tarayıcınızı http://localhost:3000/issue
adresinde açın. Artık formu
doldurabilirsiniz ve oluşturulan QR kodu, mobil cüzdanınızın bağlanmasına ve kimlik
bilgisini almasına olanak tanıyarak halka açık ngrok URL'nize doğru bir şekilde
yönlendirecektir.
ngrok
'un Önemi#Dijital kimlik bilgisi protokolleri, güvenlik en üst öncelik olacak şekilde
oluşturulmuştur. Bu nedenle, cüzdanlar neredeyse her zaman güvensiz bir (http://
)
bağlantı üzerinden bir vericiye bağlanmayı reddedecektir. Tüm süreç, bir SSL
sertifikası ile etkinleştirilen güvenli bir HTTPS bağlantısına dayanır.
ngrok
gibi bir tünel hizmeti, tüm trafiği yerel geliştirme sunucunuza yönlendiren
güvenli, halka açık bir HTTPS URL'si (geçerli bir SSL sertifikasıyla) oluşturarak her iki
sorunu da çözer. Cüzdanlar HTTPS gerektirir ve güvensiz (http://
) uç noktalara
bağlanmayı reddeder. Bu, mobil cihazlarla veya harici webhook'larla etkileşime girmesi
gereken herhangi bir web hizmetini test etmek için temel bir araçtır.
Bu örnek, anlaşılmasını kolaylaştırmak için kasıtlı olarak temel düzenleme akışına odaklanmıştır. Aşağıdaki konular kapsam dışı kabul edilir:
revoked
bayrağı
içerse de, burada herhangi bir iptal mantığı sağlanmamıştır.pre-authorized_code
akışına odaklandık.
authorization_code
akışının tam bir uygulaması, bir kullanıcı onay ekranı ve daha
karmaşık OAuth 2.0 mantığı gerektirir.İşte bu kadar! Birkaç sayfa kodla, artık aşağıdakileri yapan eksiksiz, uçtan uca bir dijital kimlik bilgisi vericimiz var:
pre-authorized_code
akışını uygular.Bu kılavuz sağlam bir temel sağlasa da, üretime hazır bir verici, sağlam anahtar yönetimi, bellek içi depolama yerine kalıcı depolama, kimlik bilgisi iptali ve kapsamlı güvenlik sıkılaştırması gibi ek özellikler gerektirir. Cüzdan uyumluluğu da değişiklik gösterir; test için Sphereon Wallet önerilir, ancak diğer cüzdanlar burada uygulanan önceden yetkilendirilmiş akışı desteklemeyebilir. Ancak, temel yapı taşları ve etkileşim akışı aynı kalacaktır. Bu kalıpları izleyerek, her tür dijital kimlik bilgisi için güvenli ve birlikte çalışabilir bir verici oluşturabilirsiniz.
İşte bu eğitimde kullanılan veya atıfta bulunulan bazı temel kaynaklar, spesifikasyonlar ve araçlar:
Proje Deposu:
Anahtar Spesifikasyonlar:
did:web
Method: Vericimizin açık
anahtarı için kullanılan DID yöntemi.Araçlar:
Kütüphaneler:
Related Articles
Table of Contents