Pelajari cara membuat verifikator kredensial digital dari awal menggunakan Next.js, OpenID4VP, dan ISO mDoc. Panduan developer langkah demi langkah ini menunjukkan cara membuat verifikator yang dapat meminta, menerima, dan memvalidasi surat izin mengemudi
Amine
Created: August 20, 2025
Updated: August 21, 2025
See the original blog version in English here.
Membuktikan identitas secara online adalah tantangan yang terus-menerus, yang menyebabkan ketergantungan pada kata sandi dan berbagi dokumen sensitif melalui saluran yang tidak aman. Hal ini membuat verifikasi identitas untuk bisnis menjadi proses yang lambat, mahal, dan rawan penipuan. Kredensial Digital menawarkan pendekatan baru, yang mengembalikan kendali data kepada pengguna. Kredensial ini adalah versi digital dari dompet fisik, yang berisi segalanya mulai dari surat izin mengemudi hingga ijazah universitas, tetapi dengan manfaat tambahan yaitu aman secara kriptografis, menjaga privasi, dan dapat diverifikasi secara instan.
Panduan ini memberikan tutorial praktis dan langkah demi langkah bagi para developer untuk membangun verifikator Kredensial Digital. Meskipun standarnya sudah ada, panduan tentang penerapannya masih sedikit. Tutorial ini mengisi kekosongan tersebut, dengan menunjukkan cara membangun verifikator menggunakan API Kredensial Digital bawaan browser, OpenID4VP untuk protokol presentasi, dan ISO mDoc (misalnya, surat izin mengemudi mobile) sebagai format kredensial.
Hasil akhirnya adalah aplikasi Next.js yang sederhana namun fungsional yang dapat meminta, menerima, dan memverifikasi Kredensial Digital dari dompet mobile yang kompatibel.
Berikut adalah gambaran singkat dari aplikasi final saat beraksi. Prosesnya melibatkan empat langkah utama:
Langkah 1: Halaman Awal Pengguna mendarat di halaman awal dan mengeklik "Verify with Digital Identity" untuk memulai proses.
Langkah 2: Permintaan Kepercayaan Browser meminta kepercayaan dari pengguna. Pengguna mengeklik "Continue" untuk melanjutkan.
Langkah 3: Pemindaian Kode QR Sebuah kode QR ditampilkan, yang dipindai oleh pengguna dengan aplikasi dompet mereka yang kompatibel.
Langkah 4: Kredensial yang Didekode Setelah verifikasi berhasil, aplikasi menampilkan data kredensial yang telah didekode.
Recent Articles
Kecanggihan di balik kredensial digital terletak pada model "segitiga kepercayaan" yang sederhana namun kuat yang melibatkan tiga pemain kunci:
Ketika seorang pengguna ingin mengakses layanan, mereka menunjukkan kredensial dari dompet mereka. Verifikator kemudian dapat langsung memeriksa keasliannya tanpa perlu menghubungi penerbit asli secara langsung.
Agar ekosistem identitas terdesentralisasi ini dapat berkembang, peran verifikator sangatlah penting. Mereka adalah penjaga gerbang dari infrastruktur kepercayaan baru ini, pihak yang mengonsumsi kredensial dan membuatnya berguna di dunia nyata. Seperti yang diilustrasikan diagram di bawah, seorang verifikator melengkapi segitiga kepercayaan dengan meminta, menerima, dan memvalidasi kredensial dari pemegang.
Jika Anda seorang developer, membangun layanan untuk melakukan verifikasi ini adalah keterampilan dasar untuk generasi aplikasi yang aman dan berpusat pada pengguna di masa depan. Panduan ini dirancang untuk memandu Anda melalui proses tersebut. Kita akan membahas semua yang perlu Anda ketahui untuk membangun verifikator kredensial terverifikasi Anda sendiri, mulai dari konsep dan standar inti hingga detail implementasi langkah demi langkah dalam memvalidasi tanda tangan dan memeriksa status kredensial.
Ingin langsung mencoba? Anda dapat menemukan proyek lengkap dan jadi untuk tutorial ini di GitHub. Silakan clone dan coba sendiri: https://github.com/corbado/digital-credentials-example
Mari kita mulai.
Sebelum memulai, pastikan Anda memiliki:
Sekarang kita akan membahas setiap prasyarat ini secara detail, dimulai dengan standar dan protokol yang mendasari verifikator berbasis mdoc ini.
Verifikator kita dibangun untuk hal-hal berikut:
Standar / Protokol | Deskripsi |
---|---|
W3C VC | W3C Verifiable Credentials Data Model. Ini mendefinisikan struktur standar untuk kredensial digital, termasuk klaim, metadata, dan bukti. |
SD-JWT | Selective Disclosure for JWTs. Format untuk VC berbasis JSON Web Tokens yang memungkinkan pemegang untuk secara selektif hanya mengungkapkan klaim tertentu dari sebuah kredensial, sehingga meningkatkan privasi. |
ISO mDoc | ISO/IEC 18013-5. Standar internasional untuk Surat Izin Mengemudi mobile (mDL) dan ID mobile lainnya, yang mendefinisikan struktur data dan protokol komunikasi untuk penggunaan offline dan online. |
OpenID4VP | OpenID for Verifiable Presentations. Protokol presentasi yang dapat dioperasikan yang dibangun di atas OAuth 2.0. Ini mendefinisikan bagaimana verifikator meminta kredensial dan bagaimana dompet pemegang mempresentasikannya. |
Untuk tutorial ini, kita secara spesifik menggunakan:
Catatan tentang Lingkup: Meskipun kita secara singkat memperkenalkan W3C VC dan SD-JWT untuk memberikan konteks yang lebih luas, tutorial ini secara eksklusif mengimplementasikan kredensial ISO mDoc melalui OpenID4VP. VC berbasis W3C berada di luar lingkup contoh ini.
Standar ISO/IEC 18013-5 mDoc mendefinisikan struktur dan pengkodean untuk dokumen mobile seperti surat izin mengemudi mobile (mDL). Kredensial mDoc dienkode dengan CBOR, ditandatangani secara kriptografis, dan dapat dipresentasikan secara digital untuk verifikasi. Verifikator kita akan fokus pada decoding dan validasi kredensial mdoc ini.
OpenID4VP adalah protokol yang dapat dioperasikan untuk meminta dan mempresentasikan kredensial digital, dibangun di atas OAuth 2.0 dan OpenID Connect. Dalam implementasi ini, OpenID4VP digunakan untuk:
Setelah kita memiliki pemahaman yang jelas tentang standar dan protokol, kita perlu memilih tech stack yang tepat untuk membangun verifikator kita. Pilihan kita dirancang untuk ketahanan, pengalaman developer, dan kompatibilitas dengan ekosistem web modern.
Kita akan menggunakan TypeScript untuk kode frontend dan backend kita. Sebagai superset dari JavaScript, ia menambahkan pengetikan statis, yang membantu menangkap kesalahan lebih awal, meningkatkan kualitas kode, dan membuat aplikasi kompleks lebih mudah dikelola. Dalam konteks yang sensitif terhadap keamanan seperti verifikasi kredensial, keamanan tipe adalah keuntungan besar.
Next.js adalah framework pilihan kita karena menyediakan pengalaman yang mulus dan terintegrasi untuk membangun aplikasi full-stack.
redirect_uri
untuk menerima dan memverifikasi
respons akhir dari CMWallet dengan aman.Implementasi kita bergantung pada serangkaian library khusus untuk frontend dan backend:
Catatan tentang openid-client
: Verifikator yang lebih canggih dan siap produksi
mungkin menggunakan library openid-client
untuk menangani protokol OpenID4VP langsung
di backend, memungkinkan fitur seperti redirect_uri
dinamis. Dalam alur OpenID4VP yang
digerakkan server dengan redirect_uri
, openid-client
akan digunakan untuk mengurai
dan memvalidasi respons vp_token
secara langsung. Untuk tutorial ini, kita menggunakan
alur yang lebih sederhana, yang dimediasi browser yang tidak memerlukannya, membuat
prosesnya lebih mudah dipahami.
Tech stack ini memastikan implementasi verifikator yang tangguh, aman-tipe, dan dapat diskalakan yang berfokus pada API Kredensial Digital browser dan format kredensial ISO mDoc.
Untuk menguji verifikator Anda, Anda memerlukan dompet mobile yang dapat berinteraksi dengan API Kredensial Digital browser.
Kita akan menggunakan CMWallet, -dompet pengujian yang tangguh dan sesuai dengan OpenID4VP untuk Android.
Cara Menginstal CMWallet (Android):
Catatan: Hanya instal file APK dari sumber yang Anda percayai. Tautan yang disediakan berasal dari repositori proyek resmi.
Sebelum kita masuk ke implementasi, penting untuk memahami konsep kriptografi yang mendasari kredensial terverifikasi. Inilah yang membuatnya "dapat diverifikasi" dan dapat dipercaya.
Pada intinya, Kredensial Terverifikasi adalah seperangkat klaim (seperti nama, tanggal lahir, dll.) yang telah ditandatangani secara digital oleh penerbit. Tanda tangan digital memberikan dua jaminan penting:
Tanda tangan digital dibuat menggunakan kriptografi kunci publik/privat (juga disebut kriptografi asimetris). Begini cara kerjanya dalam konteks kita:
Catatan tentang DID: Dalam tutorial ini, kita tidak menyelesaikan kunci penerbit melalui DID. Di lingkungan produksi, penerbit biasanya akan mengekspos kunci publik melalui DID atau endpoint otoritatif lainnya, yang akan digunakan verifikator untuk validasi kriptografis.
Kredensial Terverifikasi sering diformat sebagai JSON Web Tokens (JWTs). JWT adalah
cara yang ringkas dan aman untuk URL untuk merepresentasikan klaim yang akan ditransfer
antara dua pihak. JWT yang ditandatangani (juga dikenal sebagai JWS) memiliki tiga bagian
yang dipisahkan oleh titik (.
):
alg
).vc
), termasuk
issuer
, credentialSubject
, dll.// Contoh struktur JWT [Header].[Payload].[Signature]
Catatan: Kredensial Terverifikasi berbasis JWT berada di luar lingkup posting blog ini. Implementasi ini berfokus pada kredensial ISO mDoc dan OpenID4VP, bukan Kredensial Terverifikasi W3C atau kredensial berbasis JWT.
Tidak cukup bagi verifikator untuk mengetahui bahwa kredensial itu valid; ia juga perlu tahu bahwa orang yang mempresentasikan kredensial adalah pemegang yang sah. Ini mencegah seseorang menggunakan kredensial yang dicuri.
Ini diselesaikan menggunakan Presentasi Terverifikasi (VP). VP adalah pembungkus di sekitar satu atau lebih VC yang ditandatangani oleh pemegang itu sendiri.
Alurnya adalah sebagai berikut:
Verifikator kita kemudian harus melakukan dua pemeriksaan tanda tangan terpisah:
Cek dua tingkat ini memastikan keaslian kredensial dan identitas orang yang mempresentasikannya, menciptakan model kepercayaan yang kuat dan aman.
Catatan: Konsep Presentasi Terverifikasi seperti yang didefinisikan dalam ekosistem
W3C VC berada di luar lingkup posting blog ini. Istilah
Presentasi Terverifikasi di sini mengacu pada
respons vp_token
OpenID4VP, yang berperilaku mirip dengan VP W3C tetapi didasarkan
pada semantik ISO mDoc daripada model tanda tangan JSON-LD W3C.
Panduan ini berfokus pada kredensial ISO mDoc dan OpenID4VP, bukan pada Presentasi
Terverifikasi W3C atau validasi tanda tangan mereka.
Arsitektur verifikator kita menggunakan API Kredensial Digital bawaan browser sebagai perantara aman untuk menghubungkan aplikasi web kita dengan CMWallet mobile pengguna. Pendekatan ini menyederhanakan alur dengan membiarkan browser menangani tampilan kode QR asli dan komunikasi dompet.
navigator.credentials.get()
browser, menerima hasilnya, dan meneruskannya ke backend
kita untuk verifikasi.openid4vp
, dan secara native menghasilkan kode QR. Kemudian
menunggu dompet mengembalikan respons.Berikut adalah diagram urutan yang mengilustrasikan alur yang lengkap dan akurat:
Penjelasan Alur:
/api/verify/start
), yang
menghasilkan objek permintaan yang berisi kueri dan nonce, lalu mengembalikannya.navigator.credentials.get()
dengan
objek permintaan.openid4vp
dan secara native
menampilkan kode QR. Promise .get()
sekarang dalam status pending.Catatan: Alur kode QR ini terjadi pada browser desktop. Pada browser mobile (Android
Chrome dengan flag eksperimental diaktifkan), browser dapat langsung berkomunikasi
dengan dompet yang kompatibel di perangkat
yang sama, menghilangkan kebutuhan untuk pemindaian kode QR. Untuk mengaktifkan fitur
ini di Android Chrome, navigasikan ke chrome://flags#web-identity-digital-credentials
dan atur flag ke "Enabled".
.get()
asli di
frontend akhirnya selesai, mengirimkan payload presentasi./api/verify/finish
backend kita. Backend memvalidasi nonce dan kredensial.Setelah kita memiliki pemahaman yang kuat tentang standar, protokol, dan alur arsitektur, kita dapat mulai membangun verifikator kita.
Ikuti atau Gunakan Kode Final
Kita sekarang akan melalui penyiapan dan implementasi kode langkah demi langkah. Jika Anda lebih suka langsung ke produk jadi, Anda dapat meng-clone proyek lengkap dari repositori GitHub kami dan menjalankannya secara lokal.
git clone https://github.com/corbado/digital-credentials-example.git
Pertama, kita akan menginisialisasi proyek Next.js baru, menginstal dependensi yang diperlukan, dan memulai database kita.
Buka terminal Anda, navigasikan ke direktori tempat Anda ingin membuat proyek Anda, dan jalankan perintah berikut. Kita menggunakan App Router, TypeScript, dan Tailwind CSS untuk proyek ini.
npx create-next-app@latest . --ts --eslint --tailwind --app --src-dir --import-alias "@/*" --use-npm
Perintah ini membuat aplikasi Next.js baru di direktori Anda saat ini.
Selanjutnya, kita perlu menginstal library yang akan menangani decoding CBOR, koneksi database, dan pembuatan UUID.
npm install cbor-web mysql2 uuid @types/uuid
Perintah ini menginstal:
cbor-web
: Untuk mendekode payload kredensial mdoc.mysql2
: Klien MySQL untuk database kita.uuid
: Untuk menghasilkan string challenge yang unik.@types/uuid
: Tipe TypeScript untuk library uuid
.Backend kita memerlukan database MySQL untuk menyimpan data sesi OIDC, memastikan bahwa
setiap alur verifikasi aman dan stateful. Kami telah menyertakan file docker-compose.yml
untuk memudahkan ini.
Jika Anda telah meng-clone repositori, Anda cukup menjalankan docker-compose up -d
. Jika
Anda membangun dari awal, buat file bernama docker-compose.yml
dengan konten berikut:
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:
Pengaturan Docker Compose ini juga memerlukan skrip inisialisasi SQL. Buat direktori
bernama sql
dan di dalamnya, file bernama init.sql
dengan konten berikut untuk
menyiapkan tabel yang diperlukan:
-- 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) );
Setelah kedua file berada di tempatnya, buka terminal Anda di root proyek dan jalankan:
docker-compose up -d
Perintah ini akan memulai kontainer MySQL di latar belakang.
Aplikasi Next.js kita terstruktur untuk memisahkan urusan antara frontend dan backend, meskipun mereka adalah bagian dari proyek yang sama.
src/app/page.tsx
): Satu halaman React yang
menginisiasi alur verifikasi dan menampilkan hasilnya. Ia berinteraksi dengan API
Kredensial Digital browser.src/app/api/verify/...
):
start/route.ts
: Menghasilkan permintaan OpenID4VP dan nonce
keamanan.finish/route.ts
: Menerima presentasi dari dompet (melalui browser), memvalidasi
nonce, dan mendekode kredensial.src/lib/
):
database.ts
: Mengelola semua interaksi database (membuat challenges, memverifikasi
sesi).crypto.ts
: Menangani decoding kredensial mDoc berbasis CBOR.Berikut adalah diagram yang mengilustrasikan arsitektur internal:
Frontend kita sengaja dibuat ringan. Tanggung jawab utamanya adalah bertindak sebagai pemicu yang menghadap pengguna untuk alur verifikasi dan untuk berkomunikasi dengan backend kita dan kemampuan penanganan kredensial asli browser. Ia tidak mengandung logika protokol yang kompleks; semua itu didelegasikan.
Secara spesifik, frontend akan menangani hal berikut:
/api/verify/start
dan menerima payload
JSON terstruktur (protocol
, request
, state
) yang menjelaskan dengan tepat apa yang
harus dipresentasikan oleh dompet.navigator.credentials.get()
, yang merender kode QR asli dan menunggu respons dompet./api/verify/finish
kita dalam
permintaan POST untuk validasi sisi server akhir.Logika inti ada di dalam fungsi startVerification
:
// src/app/page.tsx const startVerification = async () => { setLoading(true); setVerificationResult(null); try { // 1. Check if the browser supports the API if (!navigator.credentials?.get) { throw new Error("Browser does not support the Credential API."); } // 2. Ask our backend for a request object const res = await fetch("/api/verify/start"); const { protocol, request } = await res.json(); // 3. Hand that object to the browser – this triggers the native QR code const credential = await (navigator.credentials as any).get({ mediation: "required", digital: { requests: [ { protocol, // "openid4vp" data: request, // contains dcql_query, nonce, etc. }, ], }, }); // 4. Forward the wallet response (from the browser) to our finish endpoint for server-side checks const verifyRes = await fetch("/api/verify/finish", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(credential), }); const result = await verifyRes.json(); if (verifyRes.ok && result.verified) { setVerificationResult(`Success: ${result.message}`); } else { throw new Error(result.message || "Verification failed."); } } catch (err) { setVerificationResult(`Error: ${(err as Error).message}`); } finally { setLoading(false); } };
Fungsi ini menunjukkan empat langkah kunci dari logika frontend: memeriksa dukungan API, mengambil permintaan dari backend, memanggil API browser, dan mengirimkan hasilnya kembali untuk verifikasi. Sisa dari file ini adalah boilerplate React standar untuk state dan rendering UI, yang dapat Anda lihat di repositori GitHub.
digital
dan mediation: 'required'
?#Anda mungkin memperhatikan panggilan kita ke navigator.credentials.get()
terlihat
berbeda dari contoh yang lebih sederhana. Ini karena kita mengikuti secara ketat
spesifikasi resmi
W3C Digital Credentials API.
digital
Member: Spesifikasi mengharuskan semua permintaan kredensial digital
disarangkan di dalam objek digital
. Ini menyediakan namespace yang jelas dan
terstandarisasi untuk API ini, membedakannya dari jenis kredensial lain (seperti
password
atau federated
) dan memungkinkan ekstensi di masa depan tanpa konflik.
mediation: 'required'
: Opsi ini adalah fitur keamanan dan pengalaman pengguna yang
krusial. Ini memberlakukan bahwa pengguna harus secara aktif berinteraksi dengan prompt
(misalnya, pemindaian biometrik, entri PIN, atau layar persetujuan) untuk menyetujui
permintaan kredensial. Tanpa itu, sebuah situs web berpotensi mencoba mengakses
kredensial secara diam-diam di latar belakang, yang menimbulkan risiko privasi yang
signifikan. Dengan mewajibkan mediasi, kita memastikan bahwa pengguna selalu memegang
kendali dan memberikan persetujuan eksplisit untuk setiap transaksi.
Dengan UI React yang sudah ada, kita sekarang membutuhkan dua rute API yang melakukan pekerjaan berat di server:
/api/verify/start
– membangun permintaan OpenID4VP, menyimpan challenge sekali
pakai di MySQL dan menyerahkan semuanya kembali ke browser./api/verify/finish
– menerima respons dompet, memvalidasi challenge,
memverifikasi & mendekode kredensial, dan akhirnya mengembalikan hasil JSON ringkas ke
UI./api/verify/start
: Hasilkan Permintaan OpenID4VP#// src/app/api/verify/start/route.ts import { NextResponse } from "next/server"; import { v4 as uuidv4 } from "uuid"; import { createChallenge, cleanupExpiredChallenges } from "@/lib/database"; export async function GET() { // 1️⃣ Buat nonce (challenge) acak yang berumur pendek const challenge = uuidv4(); const challengeId = uuidv4(); const expiresAt = new Date(Date.now() + 5 * 60 * 1000); await createChallenge(challengeId, challenge, expiresAt); cleanupExpiredChallenges().catch(console.error); // 2️⃣ Buat kueri DCQL yang menjelaskan *apa* yang kita inginkan const dcqlQuery = { credentials: [ { id: "cred1", format: "mso_mdoc", meta: { doctype_value: "eu.europa.ec.eudi.pid.1" }, claims: [ { path: ["eu.europa.ec.eudi.pid.1", "family_name"] }, { path: ["eu.europa.ec.eudi.pid.1", "given_name"] }, { path: ["eu.europa.ec.eudi.pid.1", "birth_date"] }, ], }, ], }; // 3️⃣ Kembalikan objek yang dapat diteruskan browser ke navigator.credentials.get() return NextResponse.json({ protocol: "openid4vp", // memberitahu browser protokol dompet mana yang harus digunakan request: { dcql_query: dcqlQuery, // APA yang harus dipresentasikan nonce: challenge, // anti-replay response_type: "vp_token", response_mode: "dc_api", // dompet akan melakukan POST langsung ke /finish }, state: { credential_type: "mso_mdoc", // disimpan untuk pemeriksaan nanti nonce: challenge, challenge_id: challengeId, }, }); }
Parameter kunci
• nonce
– cryptographic challenge yang
mengikat permintaan & respons (mencegah replay). • dcql_query
– Objek yang
menjelaskan klaim persis yang kita butuhkan. Untuk panduan ini, kita menggunakan
struktur dcql_query
yang terinspirasi oleh draf terbaru dari Digital Credential Query
Language, meskipun ini belum menjadi standar final. • state
– JSON acak yang
dipantulkan kembali oleh dompet sehingga kita dapat mencari catatan DB.
File src/lib/database.ts
membungkus operasi dasar MySQL untuk challenges & sesi
verifikasi (insert, read, mark-used). Menyimpan logika ini dalam satu modul membuatnya
mudah untuk menukar datastore nanti.
/api/verify/finish
: Validasi & Dekode Presentasi#// src/app/api/verify/finish/route.ts import { NextResponse, NextRequest } from "next/server"; import { v4 as uuidv4 } from "uuid"; import { getChallenge, markChallengeAsUsed, createVerificationSession, updateVerificationSession, } from "@/lib/database"; import { decodeDigitalCredential, decodeAllNamespaces } from "@/lib/crypto"; export async function POST(request: NextRequest) { const body = await request.json(); // 1️⃣ Ekstrak bagian-bagian presentasi yang dapat diverifikasi const vpTokenMap = body.vp_token ?? body.data?.vp_token; const state = body.state; const mdocToken = vpTokenMap?.cred1; // kita meminta ID ini di dcqlQuery if (!vpTokenMap || !state || !mdocToken) { return NextResponse.json( { verified: false, message: "Malformed response" }, { status: 400 }, ); } // 2️⃣ Validasi challenge sekali pakai const stored = await getChallenge(state.nonce); if (!stored) { return NextResponse.json( { verified: false, message: "Invalid or expired challenge" }, { status: 400 }, ); } const sessionId = uuidv4(); await createVerificationSession(sessionId, stored.id); // 3️⃣ Pemeriksaan kriptografi (Pseudo) – ganti dengan validasi mDL nyata di prod // Dalam aplikasi nyata, Anda akan menggunakan library khusus untuk melakukan validasi // kriptografi penuh dari tanda tangan mdoc terhadap kunci publik penerbit. const isValid = mdocToken.length > 0; if (!isValid) { await updateVerificationSession(sessionId, "failed", { reason: "mdoc validation failed", }); return NextResponse.json( { verified: false, message: "Credential validation failed" }, { status: 400 }, ); } // 4️⃣ Dekode payload mobile-DL (mdoc) menjadi JSON yang dapat dibaca manusia const decoded = await decodeDigitalCredential(mdocToken); const readable = decodeAllNamespaces(decoded)["eu.europa.ec.eudi.pid.1"]; await markChallengeAsUsed(state.nonce); await updateVerificationSession(sessionId, "verified", { readable }); return NextResponse.json({ verified: true, message: "mdoc credential verified successfully!", credentialData: readable, sessionId, }); }
Bidang penting dalam respons dompet
• vp_token
– map yang menampung setiap kredensial yang dikembalikan dompet.
Untuk demo kita, kita menarik vp_token.cred1
. • state
– gema dari blob yang kita
berikan di /start
; berisi nonce
sehingga kita dapat mencari catatan DB. •
mdocToken
– struktur CBOR yang dienkode Base64URL yang merepresentasikan ISO mDoc.
Ketika verifikator menerima kredensial mdoc dari browser, itu adalah string Base64URL yang
berisi data biner yang dienkode CBOR. Untuk mengekstrak klaim aktual, endpoint finish
melakukan proses decoding multi-langkah menggunakan fungsi pembantu dari
src/lib/crypto.ts
.
Fungsi decodeDigitalCredential
menangani konversi dari string yang dienkode menjadi
objek yang dapat digunakan:
// src/lib/crypto.ts export async function decodeDigitalCredential(encodedCredential: string) { // 1. Convert Base64URL to standard Base64 const base64UrlToBase64 = (input: string) => { let base64 = input.replace(/-/g, "+").replace(/_/g, "/"); const pad = base64.length % 4; if (pad) base64 += "=".repeat(4 - pad); return base64; }; const base64 = base64UrlToBase64(encodedCredential); // 2. Decode Base64 to binary const binaryString = atob(base64); const byteArray = Uint8Array.from(binaryString, (char) => char.charCodeAt(0)); // 3. Decode CBOR const decoded = await cbor.decodeFirst(byteArray); return decoded; }
cbor-web
untuk mendekode data biner menjadi
objek JavaScript terstruktur.Fungsi decodeAllNamespaces
memproses lebih lanjut objek CBOR yang telah didekode untuk
mengekstrak klaim aktual dari namespace yang relevan:
// src/lib/crypto.ts export function decodeAllNamespaces(jsonObj) { const decoded = {}; try { jsonObj.documents.forEach((doc, idx) => { // 1) issuerSigned.nameSpaces: const issuerNS = doc.issuerSigned?.nameSpaces || {}; Object.entries(issuerNS).forEach(([nsName, entries]) => { if (!decoded[nsName]) decoded[nsName] = {}; (entries as any[]).forEach((entry) => { const bytes = Uint8Array.from(entry.value); const decodedEntry = cbor.decodeFirstSync(bytes); Object.assign(decoded[nsName], decodedEntry); }); }); // 2) deviceSigned.nameSpaces (if present): const deviceNS = doc.deviceSigned?.nameSpaces; if (deviceNS?.value?.data) { const bytes = Uint8Array.from(deviceNS.value); decoded[`deviceSigned_ns_${idx}`] = cbor.decodeFirstSync(bytes); } }); } catch (e) { console.error(e); } return decoded; }
eu.europa.ec.eudi.pid.1
) untuk mengekstrak
nilai klaim aktual (seperti nama, tanggal lahir, dll.).Setelah melalui langkah-langkah ini, endpoint finish memperoleh objek yang dapat dibaca manusia yang berisi klaim dari mdoc, misalnya:
{ "family_name": "Doe", "given_name": "John", "birth_date": "1990-01-01" }
Proses ini memastikan bahwa verifikator dapat mengekstrak informasi yang diperlukan dari kredensial mdoc dengan aman dan andal untuk ditampilkan dan diproses lebih lanjut.
Endpoint finish mengembalikan objek JSON minimal ke frontend:
{ "verified": true, "message": "mdoc credential verified successfully!", "credentialData": { "family_name": "Doe", "given_name": "John", "birth_date": "1990-01-01" } }
Frontend menerima respons ini di startVerification()
dan hanya menyimpannya di state
React sehingga kita dapat merender kartu konfirmasi yang bagus atau menampilkan klaim
individual – mis. “Selamat datang, John Doe (lahir 1990-01-01)!”.
Anda sekarang memiliki verifikator lengkap yang berfungsi yang menggunakan kemampuan penanganan kredensial asli browser. Berikut cara menjalankannya secara lokal dan apa yang dapat Anda lakukan untuk mengembangkannya dari bukti konsep menjadi aplikasi siap produksi.
Clone Repositori:
git clone https://github.com/corbado/digital-credentials-example.git cd digital-credentials-example
Instal Dependensi:
npm install
Mulai Database: Pastikan Docker berjalan di mesin Anda, lalu mulai kontainer MySQL:
docker-compose up -d
Jalankan Aplikasi:
npm run dev
Buka browser Anda ke http://localhost:3000
, dan Anda akan melihat UI verifikator.
Anda sekarang dapat menggunakan CMWallet Anda untuk memindai kode QR dan menyelesaikan
alur verifikasi.
Tutorial ini menyediakan blok bangunan dasar untuk verifikator. Untuk membuatnya siap produksi, Anda perlu mengimplementasikan beberapa fitur tambahan:
Validasi Kriptografi Penuh: Implementasi saat ini menggunakan pemeriksaan
placeholder (mdocToken.length > 0
). Dalam skenario dunia nyata, Anda harus melakukan
validasi kriptografi penuh dari tanda tangan mdoc terhadap kunci publik
penerbit (misalnya, dengan menyelesaikan DID mereka atau mengambil
sertifikat kunci publik mereka). Untuk standar resolusi DID, lihat
spesifikasi Resolusi DID W3C.
Pemeriksaan Pencabutan Penerbit: Kredensial dapat dicabut oleh penerbit sebelum tanggal kedaluwarsanya. Verifikator produksi harus memeriksa status kredensial dengan menanyakan daftar pencabutan atau endpoint status yang disediakan oleh penerbit. W3C Verifiable Credentials Status List menyediakan standar untuk daftar pencabutan kredensial.
Penanganan Kesalahan & Keamanan yang Kuat: Tambahkan penanganan kesalahan yang komprehensif, validasi input, pembatasan laju pada endpoint API, dan pastikan semua komunikasi melalui HTTPS (TLS) untuk melindungi data saat transit. Panduan Keamanan API OWASP menyediakan praktik terbaik keamanan API yang komprehensif.
Dukungan untuk Beberapa Jenis Kredensial: Perluas logika untuk menangani nilai
doctype
dan format kredensial yang berbeda jika Anda berharap menerima lebih dari
sekadar kredensial PID Identitas Digital
Eropa (EUDI).
W3C Verifiable Credentials Data Model
menyediakan spesifikasi format VC yang komprehensif.
Contoh ini sengaja difokuskan pada alur inti yang dimediasi browser agar mudah dipahami. Topik-topik berikut dianggap di luar lingkup:
redirect_uri
atau
pendaftaran klien dinamis.Dengan membangun di atas fondasi ini dan memasukkan langkah-langkah selanjutnya ini, Anda dapat mengembangkan verifikator yang kuat dan aman yang mampu mempercayai dan memvalidasi kredensial digital di aplikasi Anda sendiri.
Selesai! Dengan kurang dari 250 baris TypeScript, kita sekarang memiliki verifikator end-to-end yang:
Di lingkungan produksi, Anda akan mengganti validasi placeholder dengan pemeriksaan ISO 18013-5 penuh, menambahkan pencarian pencabutan penerbit, pembatasan laju, logging audit, dan tentu saja, TLS end-to-end—tetapi blok bangunan inti tetap sama persis.
Berikut adalah beberapa sumber daya, spesifikasi, dan alat utama yang digunakan atau direferensikan dalam tutorial ini:
Repositori Proyek:
Spesifikasi Utama:
Alat:
Library:
Related Articles
Table of Contents