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

Cara Membuat Verifikator Kredensial Digital (Panduan Developer)

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

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. Pendahuluan#

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.

1.1 Cara Kerjanya#

Kecanggihan di balik kredensial digital terletak pada model "segitiga kepercayaan" yang sederhana namun kuat yang melibatkan tiga pemain kunci:

  • Penerbit (Issuer): Otoritas tepercaya (misalnya, lembaga pemerintah, universitas, atau bank) yang secara kriptografis menandatangani dan menerbitkan kredensial kepada pengguna.
  • Pemegang (Holder): Pengguna, yang menerima kredensial dan menyimpannya dengan aman di dompet digital pribadi di perangkat mereka.
  • Verifikator (Verifier): Aplikasi atau layanan yang perlu memeriksa kredensial pengguna.

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.

1.2 Mengapa Verifikator Penting (dan Mengapa Anda di Sini)#

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.

2. Prasyarat untuk Membangun Verifikator#

Sebelum memulai, pastikan Anda memiliki:

  1. Pemahaman Dasar tentang Kredensial Digital dan mdoc
    • Tutorial ini berfokus pada format ISO mDoc (misalnya, untuk surat izin mengemudi mobile) dan tidak mencakup format lain seperti W3C Verifiable Credentials (VCs). Memahami konsep dasar mdoc akan sangat membantu.
  2. Docker dan Docker Compose
    • Proyek kami menggunakan database MySQL dalam sebuah kontainer Docker untuk mengelola status sesi OIDC. Pastikan Anda telah menginstal dan menjalankan keduanya.
  3. Protokol yang Dipilih: OpenID4VP
    • Kita akan menggunakan protokol OpenID4VP (OpenID for Verifiable Presentations) untuk alur pertukaran kredensial.
  4. Tech Stack Siap
    • Gunakan TypeScript (Node.js) untuk logika backend.
    • Gunakan Next.js untuk backend (API routes) dan frontend (UI).
    • Library utama: library decoding CBOR untuk parsing mdoc dan klien MySQL.
  5. Kredensial dan Dompet Uji
    • Kita akan menggunakan CMWallet untuk Android, yang memahami permintaan OpenID4VP dan dapat mempresentasikan kredensial mdoc.
  6. Pengetahuan Dasar Kriptografi
    • Memahami tanda tangan digital dan konsep kunci publik/privat karena berkaitan dengan mdoc dan alur OIDC.

Sekarang kita akan membahas setiap prasyarat ini secara detail, dimulai dengan standar dan protokol yang mendasari verifikator berbasis mdoc ini.

2.1 Pilihan Protokol#

Verifikator kita dibangun untuk hal-hal berikut:

Standar / ProtokolDeskripsi
W3C VCW3C Verifiable Credentials Data Model. Ini mendefinisikan struktur standar untuk kredensial digital, termasuk klaim, metadata, dan bukti.
SD-JWTSelective 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 mDocISO/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.
OpenID4VPOpenID 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:

  • OpenID4VP sebagai protokol untuk meminta dan menerima kredensial.
  • ISO mDoc sebagai format kredensial (misalnya, untuk surat izin mengemudi mobile).

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.

2.1.1 ISO mDoc (Mobile Document)#

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.

2.1.2 OpenID4VP (OpenID for Verifiable Presentations)#

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:

  • Memulai alur presentasi kredensial (melalui kode QR atau API browser)
  • Menerima kredensial mdoc dari dompet pengguna
  • Memastikan pertukaran kredensial yang aman, stateful, dan menjaga privasi

2.2 Pilihan Tech Stack#

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.

2.2.1 Bahasa: TypeScript#

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.

2.2.2 Framework: Next.js#

Next.js adalah framework pilihan kita karena menyediakan pengalaman yang mulus dan terintegrasi untuk membangun aplikasi full-stack.

  • Untuk Frontend: Kita akan menggunakan Next.js dengan React untuk membangun antarmuka pengguna di mana proses verifikasi dimulai (misalnya, menampilkan kode QR).
  • Untuk Backend: Kita akan memanfaatkan Next.js API Routes untuk membuat endpoint sisi server. Endpoint ini bertanggung jawab untuk membuat permintaan OpenID4VP yang valid dan untuk bertindak sebagai redirect_uri untuk menerima dan memverifikasi respons akhir dari CMWallet dengan aman.

2.2.3 Library Utama#

Implementasi kita bergantung pada serangkaian library khusus untuk frontend dan backend:

  • next: Framework Next.js, digunakan untuk API routes backend dan UI frontend.
  • react dan react-dom: Menggerakkan antarmuka pengguna frontend.
  • cbor-web: Digunakan untuk mendekode kredensial mdoc yang dienkode CBOR menjadi objek JavaScript yang dapat digunakan.
  • mysql2: Menyediakan konektivitas database MySQL untuk menyimpan challenges dan sesi verifikasi.
  • uuid: Library untuk menghasilkan string challenge (nonces) yang unik.
  • @types/uuid: Tipe TypeScript untuk pembuatan UUID.

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.

2.3 Dapatkan Dompet dan Kredensial Uji#

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

  1. Unduh file APK menggunakan tautan di atas langsung di perangkat Android Anda.
  2. Buka Settings > Security di perangkat Anda.
  3. Aktifkan "Install unknown apps" untuk browser yang Anda gunakan untuk mengunduh file.
  4. Cari APK yang diunduh di folder "Downloads" Anda dan ketuk untuk memulai instalasi.
  5. Ikuti petunjuk di layar untuk menyelesaikan instalasi.
  6. Buka CMWallet, dan Anda akan menemukannya sudah terisi dengan kredensial uji, siap untuk alur verifikasi.

Catatan: Hanya instal file APK dari sumber yang Anda percayai. Tautan yang disediakan berasal dari repositori proyek resmi.

2.4 Pengetahuan Kriptografi#

Sebelum kita masuk ke implementasi, penting untuk memahami konsep kriptografi yang mendasari kredensial terverifikasi. Inilah yang membuatnya "dapat diverifikasi" dan dapat dipercaya.

2.4.1 Tanda Tangan Digital: Fondasi Kepercayaan#

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:

  • Keaslian (Authenticity): Ini membuktikan bahwa kredensial tersebut memang dibuat oleh penerbit dan bukan oleh penipu.
  • Integritas (Integrity): Ini membuktikan bahwa kredensial tersebut belum diubah atau dirusak sejak ditandatangani.

2.4.2 Kriptografi Kunci Publik/Privat#

Tanda tangan digital dibuat menggunakan kriptografi kunci publik/privat (juga disebut kriptografi asimetris). Begini cara kerjanya dalam konteks kita:

  1. Penerbit memiliki sepasang kunci: kunci privat, yang dirahasiakan, dan kunci publik, yang tersedia untuk semua orang (biasanya melalui Dokumen DID mereka).
  2. Penandatanganan: Ketika penerbit membuat kredensial, mereka menggunakan kunci privat mereka untuk menghasilkan tanda tangan digital yang unik untuk data kredensial spesifik tersebut.
  3. Verifikasi: Ketika verifikator kita menerima kredensial, ia menggunakan kunci publik penerbit untuk memeriksa tanda tangan. Jika pemeriksaan berhasil, verifikator tahu kredensial itu asli dan belum dirusak. Perubahan apa pun pada data kredensial akan membatalkan tanda tangan.

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.

2.4.3 Kredensial Terverifikasi sebagai JWT#

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

  • Header: Berisi metadata tentang token, seperti algoritma penandatanganan yang digunakan (alg).
  • Payload: Berisi klaim aktual dari Kredensial Terverifikasi (klaim vc), termasuk issuer, credentialSubject, dll.
  • Signature: Tanda tangan digital yang dihasilkan oleh penerbit, yang mencakup header dan payload.
// 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.

2.4.4 Presentasi Terverifikasi: Membuktikan Kepemilikan#

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:

  1. Verifikator meminta pengguna untuk mempresentasikan kredensial.
  2. Dompet pengguna membuat Presentasi Terverifikasi, membundel kredensial yang diperlukan di dalamnya, dan menandatangani seluruh presentasi menggunakan kunci privat pemegang.
  3. Dompet mengirimkan VP yang ditandatangani ini ke verifikator.

Verifikator kita kemudian harus melakukan dua pemeriksaan tanda tangan terpisah:

  1. Verifikasi Kredensial: Periksa tanda tangan pada setiap VC di dalam presentasi menggunakan kunci publik penerbit. (Membuktikan kredensial itu nyata).
  2. Verifikasi Presentasi: Periksa tanda tangan pada VP itu sendiri menggunakan kunci publik pemegang. (Membuktikan orang yang mempresentasikannya adalah pemiliknya).

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.

3. Tinjauan Arsitektur#

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.

  • Frontend (Next.js & React): Situs web ringan yang menghadap pengguna. Tugasnya adalah mengambil objek permintaan dari backend kita, meneruskannya ke API navigator.credentials.get() browser, menerima hasilnya, dan meneruskannya ke backend kita untuk verifikasi.
  • Backend (Next.js API Routes): Pusat kekuatan verifikator. Ini menghasilkan objek permintaan yang valid untuk API browser dan mengekspos endpoint untuk menerima presentasi kredensial dari frontend untuk validasi akhir.
  • Browser (Credential API): Fasilitator. Ini menerima objek permintaan dari frontend kita, memahami protokol openid4vp, dan secara native menghasilkan kode QR. Kemudian menunggu dompet mengembalikan respons.
  • CMWallet (Aplikasi Mobile): Dompet pengguna. Ini memindai kode QR, memproses permintaan, mendapatkan persetujuan pengguna, dan mengirimkan respons yang ditandatangani kembali ke browser.

Berikut adalah diagram urutan yang mengilustrasikan alur yang lengkap dan akurat:

Penjelasan Alur:

  1. Inisiasi: Pengguna mengeklik tombol "Verify" di Frontend kita.
  2. Objek Permintaan: Frontend memanggil Backend kita (/api/verify/start), yang menghasilkan objek permintaan yang berisi kueri dan nonce, lalu mengembalikannya.
  3. Panggilan API Browser: Frontend memanggil navigator.credentials.get() dengan objek permintaan.
  4. Kode QR Asli: Browser melihat permintaan protokol 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".

  1. Pindai & Presentasikan: Pengguna memindai kode QR dengan CMWallet. Dompet mendapatkan persetujuan pengguna dan mengirimkan Presentasi Terverifikasi kembali ke browser.
  2. Penyelesaian Promise: Browser menerima respons, dan promise .get() asli di frontend akhirnya selesai, mengirimkan payload presentasi.
  3. Verifikasi Backend: Frontend melakukan POST payload presentasi ke endpoint /api/verify/finish backend kita. Backend memvalidasi nonce dan kredensial.
  4. Hasil: Backend mengembalikan pesan keberhasilan atau kegagalan akhir ke frontend, yang memperbarui UI.

4. Membangun Verifikator#

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

4.1 Menyiapkan Proyek#

Pertama, kita akan menginisialisasi proyek Next.js baru, menginstal dependensi yang diperlukan, dan memulai database kita.

4.1.1 Menginisialisasi Aplikasi Next.js#

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.

4.1.2 Menginstal Dependensi#

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.

4.1.3 Memulai Database#

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.

4.2 Tinjauan Arsitektur Aplikasi Next.js#

Aplikasi Next.js kita terstruktur untuk memisahkan urusan antara frontend dan backend, meskipun mereka adalah bagian dari proyek yang sama.

  • Frontend (src/app/page.tsx): Satu halaman React yang menginisiasi alur verifikasi dan menampilkan hasilnya. Ia berinteraksi dengan API Kredensial Digital browser.
  • Backend API Routes (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.
  • Library (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:

4.3 Membangun Frontend#

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:

  • Interaksi Pengguna: Menyediakan antarmuka sederhana, seperti tombol "Verify", bagi pengguna untuk memulai proses.
  • Manajemen State: Mengelola state UI, menunjukkan indikator pemuatan saat verifikasi sedang berlangsung dan menampilkan pesan keberhasilan atau kesalahan akhir.
  • Komunikasi Backend (Permintaan): Memanggil /api/verify/start dan menerima payload JSON terstruktur (protocol, request, state) yang menjelaskan dengan tepat apa yang harus dipresentasikan oleh dompet.
  • Pemanggilan API Browser: Menyerahkan objek JSON tersebut ke navigator.credentials.get(), yang merender kode QR asli dan menunggu respons dompet.
  • Komunikasi Backend (Respons): Setelah API browser mengembalikan Presentasi Terverifikasi, ia mengirimkan data ini ke endpoint /api/verify/finish kita dalam permintaan POST untuk validasi sisi server akhir.
  • Menampilkan Hasil: Memperbarui UI untuk memberi tahu pengguna apakah verifikasi berhasil atau gagal, berdasarkan respons dari backend.

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.

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

4.4 Membangun Endpoint Backend#

Dengan UI React yang sudah ada, kita sekarang membutuhkan dua rute API yang melakukan pekerjaan berat di server:

  1. /api/verify/start – membangun permintaan OpenID4VP, menyimpan challenge sekali pakai di MySQL dan menyerahkan semuanya kembali ke browser.
  2. /api/verify/finish – menerima respons dompet, memvalidasi challenge, memverifikasi & mendekode kredensial, dan akhirnya mengembalikan hasil JSON ringkas ke UI.

4.4.1 /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

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

4.4.2 Helper Database#

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.


4.5 /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.

4.6 Mendekode Kredensial 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.

4.6.1 Langkah 1: Decoding Base64URL dan CBOR#

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; }
  • Base64URL ke Base64: Mengonversi kredensial dari Base64URL ke pengkodean Base64 standar.
  • Base64 ke Biner: Mendekode string Base64 menjadi array byte biner.
  • Decoding CBOR: Menggunakan library cbor-web untuk mendekode data biner menjadi objek JavaScript terstruktur.

4.6.2 Langkah 2: Mengekstrak Klaim Bernamespace#

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; }
  • Mengulangi semua dokumen dalam kredensial yang telah didekode.
  • Mendekode setiap namespace (misalnya, eu.europa.ec.eudi.pid.1) untuk mengekstrak nilai klaim aktual (seperti nama, tanggal lahir, dll.).
  • Menangani namespace yang ditandatangani penerbit dan perangkat jika ada.

Contoh Output#

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.

4.7 Menampilkan Hasil di UI#

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)!”.

5. Menjalankan Verifikator dan Langkah Selanjutnya#

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.

5.1 Cara Menjalankan Contoh#

  1. Clone Repositori:

    git clone https://github.com/corbado/digital-credentials-example.git cd digital-credentials-example
  2. Instal Dependensi:

    npm install
  3. Mulai Database: Pastikan Docker berjalan di mesin Anda, lalu mulai kontainer MySQL:

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

5.2 Langkah Selanjutnya: Dari Demo ke Produksi#

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.

5.3 Apa yang di Luar Lingkup Tutorial Ini#

Contoh ini sengaja difokuskan pada alur inti yang dimediasi browser agar mudah dipahami. Topik-topik berikut dianggap di luar lingkup:

  • Keamanan Siap Produksi: Verifikator ini untuk tujuan pendidikan dan tidak memiliki pengerasan yang diperlukan untuk lingkungan live.
  • W3C Verifiable Credentials: Tutorial ini berfokus secara eksklusif pada format ISO mDoc untuk surat izin mengemudi mobile. Ini tidak mencakup format populer lainnya seperti JWT-VC atau VC dengan Linked Data Proofs (LD-Proofs).
  • Alur OpenID4VP Lanjutan: Kita tidak mengimplementasikan fitur OpenID4VP yang lebih kompleks, seperti komunikasi langsung dompet-ke-backend menggunakan 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.

Kesimpulan#

Selesai! Dengan kurang dari 250 baris TypeScript, kita sekarang memiliki verifikator end-to-end yang:

  1. Mempublikasikan permintaan untuk API kredensial browser.
  2. Membiarkan dompet yang kompatibel menyediakan Presentasi Terverifikasi.
  3. Memvalidasi presentasi di server.
  4. Memperbarui UI secara real-time.

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.

Sumber Daya#

Berikut adalah beberapa sumber daya, spesifikasi, dan alat utama yang digunakan atau direferensikan dalam tutorial ini:

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

Start Free Trial

Share this article


LinkedInTwitterFacebook