Get your free and exclusive 80-page Banking Passkey Report
Back to Overview

Dijital Kimlik Bilgisi Vericisi Nasıl Oluşturulur (Geliştirici Kılavuzu)

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

Blog-Post-Header-Image

See the original blog version in English here.

DigitalCredentialsDemo Icon

Want to experience digital credentials in action?

Try Digital Credentials

1. Giriş#

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:

  1. Basit bir web formu aracılığıyla kullanıcı verilerini kabul etme.
  2. Güvenli, tek kullanımlık bir kimlik bilgisi teklifi oluşturma.
  3. Teklifi, kullanıcının mobil cüzdanı ile tarayabilmesi için bir QR kodu olarak görüntüleme.
  4. Kullanıcının saklayabileceği ve doğrulama için sunabileceği, kriptografik olarak imzalanmış bir kimlik bilgisi düzenleme.

1.1 Terminolojiyi Anlamak: Dijital Kimlik Bilgileri ve Doğrulanabilir Kimlik Bilgileri#

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:

    • Özgünlük ve bütünlük için kriptografik imzalar
    • Standartlaştırılmış veri modeli ve formatlar
    • Gizliliği koruyan sunum mekanizmaları
    • Birlikte çalışabilir doğrulama protokolleri

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.

1.2 Nasıl Çalışır?#

Dijital kimlik bilgilerinin arkasındaki sihir, üç kilit oyuncuyu içeren basit ama güçlü bir "güven üçgeni" modelinde yatmaktadır:

  • Verici (Issuer): Bir devlet kurumu, üniversite veya banka gibi güvenilir bir otorite, bir kullanıcıya kriptografik olarak imzalanmış bir kimlik bilgisi düzenler. Bu kılavuzda inşa ettiğimiz rol budur.
  • Sahip (Holder): Kimlik bilgisini alan ve cihazındaki kişisel dijital cüzdanında güvenli bir şekilde saklayan kullanıcı.
  • Doğrulayıcı (Verifier): Kullanıcının kimlik bilgisini kontrol etmesi gereken bir uygulama veya hizmet.

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.

2. Bir Verici Oluşturmak İçin Ön Koşullar#

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.

2.1 Protokol Seçimleri#

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 / ProtokolAçıklama
OpenID4VCIOpenID 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-VCJWT 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 mDocISO/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.0OpenID4VCI 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.

2.1.1 Yetkilendirme Akışları: Önceden Yetkilendirilmiş ve Yetkilendirme Kodu#

OpenID4VCI, kimlik bilgileri düzenlemek için iki ana yetkilendirme akışını destekler:

  1. Ö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.

  2. 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.

2.2 Teknoloji Yığını Seçimleri#

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.

2.2.1 Dil: TypeScript#

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.

2.2.2 Çerçeve: Next.js#

Next.js, tam yığın uygulamalar oluşturmak için sorunsuz, entegre bir deneyim sağladığı için tercih ettiğimiz çerçevedir.

  • Ön Uç İçin: Kullanıcıların bir kimlik bilgisi talep etmek için verilerini girebilecekleri kullanıcı arayüzünü oluşturmak için React ile Next.js kullanacağız.
  • Arka Uç İçin: Kimlik bilgisi teklifleri oluşturmaktan son imzalı kimlik bilgisini düzenlemeye kadar OpenID4VCI akışını yöneten sunucu tarafı uç noktaları oluşturmak için Next.js API Rotalarından yararlanacağız.

2.2.3 Anahtar Kütüphaneler#

Uygulamamız, belirli görevleri yerine getirmek için birkaç anahtar kütüphaneye dayanacaktır:

  • next, react, ve react-dom: Next.js uygulamamızın temel kütüphaneleri.
  • mysql2: Yetkilendirme kodlarını ve oturum verilerini saklamak için kullanılan Node.js için bir MySQL istemcisi.
  • uuid: pre-authorized_code değerleri oluşturmak için kullanacağımız benzersiz tanımlayıcılar üretmek için bir kütüphane.
  • jose: Düzenlediğimiz kimlik bilgilerini kriptografik olarak imzalamak için kullanacağımız JSON Web İmzalarını (JWS) işlemek için sağlam bir kütüphane.

2.3 Bir Test Cüzdanı Edinin#

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:

  1. Cüzdanı Google Play Store veya Apple App Store'dan indirin.
  2. Uygulamayı mobil cihazınıza kurun.
  3. Kurulduktan sonra, cüzdan bir QR kodu tarayarak kimlik bilgisi teklifleri almaya hazırdır.

2.4 Kriptografi Bilgisi#

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.

2.4.1 Dijital İmzalar#

Özünde, bir Doğrulanabilir Kimlik Bilgisi, Verici tarafından dijital olarak imzalanmış bir dizi beyandır. Bu imza iki güvence sağlar:

  • Özgünlük: Kimlik bilgisinin meşru bir Verici tarafından oluşturulduğunu kanıtlar.
  • Bütünlük: Kimlik bilgisinin düzenlendiğinden beri kurcalanmadığını kanıtlar.

2.4.2 Açık/Özel Anahtar Kriptografisi#

Dijital imzalar, açık/özel anahtar kriptografisi kullanılarak oluşturulur. İşte nasıl çalıştığı:

  1. Vericinin bir anahtar çifti vardır: gizli ve güvende tutulan bir özel anahtar ve buna karşılık gelen, halka açık hale getirilen bir açık anahtar.
  2. İmzalama: Verici bir kimlik bilgisi oluşturduğunda, kimlik bilgisi verileri için benzersiz bir dijital imza oluşturmak üzere özel anahtarını kullanır.
  3. Doğrulama: Bir Doğrulayıcı daha sonra imzayı kontrol etmek için Verici'nin açık anahtarını kullanabilir. Kontrol geçerse, Doğrulayıcı kimlik bilgisinin özgün olduğunu ve değiştirilmediğini bilir.

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.

3. Mimari Genel Bakış#

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.

  • Ön Uç (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.
  • Arka Uç API Rotaları (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.
  • Kütüphane (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:

4. Vericiyi Oluşturma#

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

4.1 Projeyi Kurma#

İ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.

4.1.1 Next.js Uygulamasını Başlatma#

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.

4.1.2 Bağımlılıkları Yükleme#

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.

4.1.3 Veritabanını Başlatma#

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.

4.2 Paylaşılan Kütüphaneleri Uygulama#

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.

4.2.1 Veritabanı Kütüphanesi (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.

4.2.2 Kripto Kütüphanesi (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.

4.2 Next.js Uygulamasının Mimari Genel Bakışı#

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/...):

    • Keşif (.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.
    • Düzenleme (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.
    • Şema (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.

4.3 Ön Ucu Oluşturma#

Ö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:

  • Kullanıcı verilerini (ad, doğum tarihi vb.) yakalamak.
  • Bu verileri, bir kimlik bilgisi teklifi oluşturmak için arka ucumuzdaki API'ye göndermek.
  • Sonuçta ortaya çıkan QR kodunu ve PIN'i, kullanıcının cüzdanıyla taraması için görüntülemek.

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:

  1. Form verilerini doğrular ve gerekli tüm alanların doldurulduğundan emin olur.
  2. Kullanıcının verileriyle birlikte /api/issue/authorize uç noktamıza bir POST isteği gönderir.
  3. Arka uçtan alınan kimlik bilgisi teklifiyle bileşenin durumunu günceller, bu da kullanıcı arayüzünün QR kodunu ve işlem kodunu görüntülemesini tetikler.

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.

4.4 Ortamı ve Keşfi Ayarlama#

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.

4.4.1 Ortam Dosyasını Oluşturma#

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

4.4.2 Keşif Uç Noktalarını Uygulama#

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.

4.4.3 Kimlik Bilgisi Şeması Uç Noktasını Uygulama#

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.

4.5 Arka Uç Uç Noktalarını Oluşturma#

Ö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.

4.5.1 /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:

  1. Veriyi Doğrulama: İlk olarak gerekli kullanıcı verilerinin mevcut olduğundan emin olur.
  2. Kodları Oluşturma: Benzersiz bir pre-authorized_code (bir UUID) ve ek bir güvenlik katmanı için 4 basamaklı bir tx_code (PIN) oluşturur.
  3. Veriyi Saklama: 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.
  4. Teklifi Oluşturma: OpenID4VCI spesifikasyonuna göre 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.
  5. URI'yi Döndürme: Son olarak, bir derin bağlantı URI'si (openid-credential-offer://...) oluşturur ve kullanıcıya göstermek için tx_code ile birlikte ön uca döndürür.

4.5.2 /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:

  1. Grant Türünü Doğrulama: Cüzdanın doğru pre-authorized_code grant türünü kullandığından emin olur.
  2. Kodu Doğrulama: pre-authorized_code'un veritabanında mevcut olduğunu, süresinin dolmadığını ve daha önce kullanılmadığını kontrol eder.
  3. PIN'i Doğrulama: Kullanıcının işlemi yetkilendirdiğinden emin olmak için cüzdandan gelen user_pin'i daha önce sakladığımız tx_code ile karşılaştırır.
  4. Token'ları Oluşturma: Güvenli bir 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.
  5. Oturum Oluşturma: Erişim tokenını kullanıcının verileriyle ilişkilendirerek veritabanında yeni bir issuance_sessions kaydı oluşturur.
  6. Kodu Kullanıldı Olarak İşaretleme: Aynı teklifin iki kez kullanılmasını önlemek için pre-authorized_code'u kullanıldı olarak işaretler.
  7. Token'ı Döndürme: access_token ve c_nonce'ı cüzdana döndürür.

4.5.3 /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:

  1. Token'ı Doğrulama: Authorization başlığında geçerli bir Bearer tokenı olup olmadığını kontrol eder ve aktif düzenleme oturumunu bulmak için kullanır.
  2. Kullanıcı Verilerini Alma: Token oluşturulduğunda oturumda saklanan kullanıcının beyan verilerini alır.
  3. Verici Anahtarını Yükleme: Vericinin aktif imzalama anahtarını veritabanından yükler. Gerçek dünya senaryosunda, bu güvenli bir anahtar yönetim sistemi tarafından yönetilirdi.
  4. Kimlik Bilgisini Oluşturma: JWT-VC'yi oluşturmak ve imzalamak için src/lib/crypto.ts dosyasındaki createJWTVerifiableCredential yardımcımızı çağırır.
  5. Düzenlemeyi Kaydetme: Denetim ve iptal amaçları için düzenlenen kimlik bilgisinin bir kaydını veritabanına kaydeder.
  6. Kimlik Bilgisini Döndürme: İmzalı kimlik bilgisini bir JSON yanıtıyla cüzdana döndürür. Cüzdan daha sonra bunu güvenli bir şekilde saklamaktan sorumludur.

5. Vericiyi Çalıştırma ve Sonraki Adımlar#

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.

5.1 Örnek Nasıl Çalıştırılır#

  1. Depoyu Klonlayın:

    git clone https://github.com/corbado/digital-credentials-example.git cd digital-credentials-example
  2. Bağımlılıkları Yükleyin:

    npm install
  3. Veritabanını Başlatın: Docker'ın çalıştığından emin olun, ardından MySQL container'ını başlatın:

    docker-compose up -d
  4. 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>
  5. 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.

5.2 HTTPS ve 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.

5.3 Bu Eğitimin Kapsamı Dışındakiler#

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:

  • Üretime Hazır Güvenlik: Bu verici eğitim amaçlıdır. Bir üretim sistemi, veritabanında anahtar saklamak yerine güvenli bir Anahtar Yönetim Sistemi (KMS), sağlam hata yönetimi, hız sınırlaması ve kapsamlı denetim günlüğü gerektirir.
  • Kimlik Bilgisi İptali: Bu kılavuz, düzenlenen kimlik bilgilerini iptal etmek için bir mekanizma uygulamamaktadır. Şema gelecekteki kullanım için bir revoked bayrağı içerse de, burada herhangi bir iptal mantığı sağlanmamıştır.
  • Yetkilendirme Kodu Akışı: Yalnızca 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.
  • Kullanıcı Yönetimi: Kılavuz, vericinin kendisi için herhangi bir kullanıcı kimlik doğrulaması veya yönetimi içermemektedir. Kullanıcının zaten kimliğinin doğrulandığı ve bir kimlik bilgisi almaya yetkili olduğu varsayılmaktadır.

6. Sonuç#

İşte bu kadar! Birkaç sayfa kodla, artık aşağıdakileri yapan eksiksiz, uçtan uca bir dijital kimlik bilgisi vericimiz var:

  1. Kimlik bilgileri talep etmek için kullanıcı dostu bir ön uç sağlar.
  2. Tam OpenID4VCI pre-authorized_code akışını uygular.
  3. Cüzdan birlikte çalışabilirliği için gerekli tüm keşif uç noktalarını sunar.
  4. Güvenli, standartlara uygun bir JWT-Doğrulanabilir Kimlik Bilgisi oluşturur ve imzalar.

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.

7. Kaynaklar#

İşte bu eğitimde kullanılan veya atıfta bulunulan bazı temel kaynaklar, spesifikasyonlar ve araçlar:

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

Start Free Trial

Share this article


LinkedInTwitterFacebook