Skip to content

Face Liveness Challenge

By Romi Fadli, S.Kom. 10 min read Security & Secrets
On this page
Three camera frames of a face, facing forward, turned right, and turned left, each checked off, flowing into locked private storage that a reviewer looks into

Face Liveness Challenge adalah service Python yang meminta orang di depan kamera membuktikan bahwa ia ada di sana, hidup, pada saat itu: hadap ke kamera, menoleh ke kanan, menoleh ke kiri. Ia mengikuti setiap gerakan frame demi frame, menangkap satu gambar tajam di tiap pose, lalu menyimpan ketiga gambar itu sebagai bukti untuk peninjau manusia. Ia tidak mengenali wajah dan tidak membandingkannya dengan apa pun — tugasnya menunjukkan bahwa orang hidup menjalankan tantangan itu, bukan siapa orangnya.

Saya membangunnya untuk aplikasi yang butuh kehadiran seseorang sebelum tindakan penting, seperti menandatangani dokumen, dan yang tetap menempatkan manusia dalam keputusannya. Satu foto yang di-upload tak membuktikan apa-apa: bisa saja itu foto dari foto. Urutan gerakan singkat yang dipandu, dilakukan langsung dan direkam sebagai tiga frame, memberi peninjau bukti yang tak bisa diberikan foto diam.

Sekilas

Bahasa & frameworkPython, dengan FastAPI, MediaPipe untuk landmark wajah, dan OpenCV
ArsitekturTransaction Script Service
Versi2.15.1
StatusStabil, berjalan di production
BentukService HTTP dan WebSocket dengan widget kamera yang bisa disematkan, dikirim sebagai satu executable hasil bundel
Kode sumberPrivat
Apa module ini dan bagaimana keadaannya.

Yang dikerjakannya

A. Memandu seseorang melewati tiga pose

Tantangannya tiga langkah dengan urutan tetap: hadap ke kamera, menoleh ke kanan, menoleh ke kiri. Untuk setiap frame, service memberi tahu widget pose apa yang dilihatnya, pose apa yang diharapkan, dan sejauh mana langkah yang sedang berjalan, sehingga orang itu selalu tahu apa yang harus dilakukan berikutnya.

B. Menangkap satu frame tajam per pose

Pose harus ditahan sejenak lalu dijaga tetap diam sebelum frame-nya disimpan, sehingga buktinya tak pernah berupa gambar buram yang tertangkap di tengah gerakan. Bergerak terlalu cepat menunda tangkapan, tak mengulang langkah dari awal.

C. Menolak tangkapan yang tak berguna

Di langkah hadap depan, detektor masker memeriksa wajah sebelum frame disimpan, karena peninjau tak bisa menilai wajah yang tertutup masker.

D. Menjaga bukti tetap privat dan menyerahkannya ke peninjau

Ketiga frame masuk ke bucket storage privat lewat link upload berumur pendek. Ketika peninjau membuka kasusnya, aplikasi meminta service link baca berumur pendek ke tiga frame terbaru milik orang itu.

E. Bisa ditaruh di halaman mana pun sebagai widget

Kamera, panduan, dan koneksinya tinggal dalam satu halaman mandiri yang disematkan aplikasi di dalam frame. Widget memberi tahu halaman induk ketika tantangan berhasil, kedaluwarsa, atau gagal, dan halaman induk cukup membuka dan menutupnya.

Antarmuka

KemampuanKegunaan
Memulai tantanganuntuk user yang sedang login; mengembalikan kode sesi, daftar pose, dan waktu kedaluwarsa
Aliran kameraframe masuk, panduan keluar, sukses di akhir
Statusapakah tantangan sudah selesai
Widget kamerauntuk disematkan di halaman aplikasi
Readinesspenyimpan sesi dan storage
Antarmuka service ini.

Untuk setiap frame yang diterimanya, service menjawab dengan status yang diubah widget menjadi panduan:

json
{
  "type": "status",
  "face_detected": true,
  "current_pose": "right",
  "expected_pose": "right",
  "current_step": 2,
  "total_steps": 3,
  "capture_phase": "capturing",
  "stability_percent": 60,
  "step_captured": false,
  "message": "Hold still…"
}

Standar

StandarYang dijaminnya di sini
WebSocket (RFC 6455)satu koneksi terbuka untuk frame dan panduan, dengan latensi cukup rendah untuk mengikuti kepala yang bergerak
JSON Web Token dengan RS256, key dari endpoint JWKStantangan milik user yang sedang login, diperiksa issuer, audience, dan client-nya
Presigned URL, AWS Signature Version 4bukti ditulis dan dibaca lewat link berumur pendek, tanpa kredensial storage di dalam service
W3C Trace Context dan OpenTelemetrysatu tantangan tampil di trace aplikasi yang memulainya
Standar yang dipatuhi service, dan yang dijamin masing-masing.

Di dalamnya

Service ini berbentuk Transaction Script Service. Endpoint WebSocket-nya adalah satu prosedur yang membaca setiap langkah tantangan secara berurutan — periksa token, muat sesi, ikuti frame, upload tangkapan, catat hasilnya — dan kerja tiap langkah tinggal di module-nya sendiri: mesin wajah beserta pelacak tantangannya, detektor masker, autentikasi, penyimpan sesi, client storage, dan telemetry. Ia dibundel menjadi satu executable bersama model-modelnya.

Keputusan rancangan

Liveness, bukan identitas, dan manusia yang memutuskan. Service menjawab satu pertanyaan — apakah orang hidup baru saja menjalankan tantangan ini — dan menyerahkan keputusan tentang siapa orangnya kepada peninjau yang melihat ketiga frame. Dengan begitu service jujur tentang apa yang bisa dibuktikannya, dan penilaian tetap berada di tempat tanggung jawab memang berada. Ia menghasilkan bukti, bukan vonis, dan log hanya membawa referensi ke frame yang tersimpan, tak pernah gambarnya.

Tiga pose menggantikan pola sembilan titik. Tantangan pertama meminta orang menggerakkan ujung hidung melewati tiga dari sembilan titik di layar, dengan jalur acak seperti pola buka kunci ponsel, dan grid-nya bahkan harus dikecilkan supaya wajah tetap di dalam panduan. Orang menganggapnya tak intuitif, jadi saya menggantinya dengan tiga pose kepala yang langsung dipahami siapa pun. Struktur pelacaknya — tahan, tenang, maju — dan kontrak output-nya sengaja saya pertahankan, dan hanya pemeriksaan apakah pose saat ini benar yang diganti, sehingga semua yang sudah terbukti soal tangkapan yang tajam ikut terbawa.

Setiap pemeriksaan harus membuktikan tempatnya. Seiring waktu, tantangan itu juga mewajibkan mata terbuka, tanpa kacamata hitam, dan senyum di langkah hadap depan. Pengukuran dengan model landmark yang asli menunjukkan bahwa pemeriksaan senyumlah sumber kesulitannya: senyum lebar mendapat skor 0,787 terhadap ambang 0,35, tapi senyum wajar dengan bibir tertutup jauh di bawahnya, sehingga orang terpaksa membuka mulut supaya lolos. Dua penjelasan saya sendiri gugur saat diuji, dan itu mencegah saya melonggarkan bagian yang salah. Saya membuang pemeriksaan senyum, mata, dan kacamata, dan hanya mempertahankan pemeriksaan masker — satu-satunya yang menjaga kemampuan peninjau melihat wajah — lalu mengganti nama pose pertama dari "senyum" menjadi "depan", karena nama yang berbohong lebih berbahaya daripada fitur yang hilang.

Arah ditentukan oleh kamera sungguhan, dan tangkapan diambil sesudah bukti diam. Arah menoleh diestimasi dari letak ujung hidung di antara kedua tepi wajah, dari satu inferensi landmark per frame. Versi pertama membalik kiri dan kanan: penalaran dari frame yang dicerminkan ternyata keliru, dan hanya uji dengan kamera sungguhan yang bisa memastikan tanda mana yang berarti kanan bagi orang itu — kodenya kini menyatakan hal itu. Saya menurunkan ambang menoleh bertahap karena orang merasa pose-nya sulit dicapai. Frame baru disimpan setelah pose ditahan lalu tetap diam, dan bunyi shutter hanya berbunyi untuk frame itu; dulu bunyi itu terdengar saat frame diambil sementara, sehingga orang mendengar tiga shutter padahal baru satu langkah yang tersimpan.

Diam diukur sebagai kecepatan, bukan sebagai jarak dari titik awal. Satu rilis membuat pose pertama mustahil diselesaikan: ia membandingkan setiap frame dengan posisi di awal tangkapan, sehingga getaran kepala yang biasa menumpuk sampai melewati batas. Simulasi menunjukkan bahwa dengan getaran dua puluh piksel, tak satu tangkapan pun terjadi. Pelacak kini mengukur seberapa cepat hidung atau tolehan bergerak, menghaluskannya, dan cukup menunggu bila terlalu cepat, tanpa mengulang langkah dari awal.

Widget memandu mata dan telinga. Halaman kamera berdiri sendiri, karena logika yang terbelah antara halaman induk dan frame membuat keduanya tak lagi seirama. Oval panduan meminta orang mendekat atau mundur, panah menunjukkan arah tolehan, dan bunyi — tik selama pose salah, shutter ketika frame disimpan, denting di akhir — membantu orang yang sedang memalingkan wajah dari layar. Panduan digambar begitu tantangan dimulai, tanpa menunggu koneksi, dan frame yang tertangkap tak pernah ditunjukkan kembali ke orangnya: frame itu bukti untuk peninjau, bukan pratinjau.

Bukti tak boleh hilang diam-diam. Ketiga frame di-upload lewat satu permintaan untuk tiga link upload ke bucket privat, masing-masing dengan content type yang eksplisit, karena tanpa itu storage mencatatnya sebagai data form. Bila satu upload gagal, yang sudah tertulis dihapus supaya tak ada set yang setengah jadi. Saat link baca untuk peninjau diminta, frame yang tak bisa ditandatangani dilaporkan sebagai hilang dan kegagalan total sebagai error; versi sebelumnya cukup mengembalikan daftar yang lebih pendek, dan untuk bukti, itu lebih buruk daripada kegagalan yang terlihat.

Tantangan milik orang yang memulainya. Setiap request diautentikasi dengan token yang ditandatangani identity service milik aplikasi, yang key-nya diambil dan di-cache, lalu diperiksa issuer, audience, dan client-nya. Token di koneksi kamera diperiksa sebelum koneksi diterima, dan orang di koneksi itu harus sama dengan orang yang memulai tantangan.

Telemetry yang bisa dipercaya lengkap. Telemetry pindah dari menulis langsung ke Elasticsearch menjadi OpenTelemetry, diinstrumentasi lewat kode karena executable hasil bundel tak punya launcher. Berbulan-bulan, panggilan ke storage tak pernah muncul di trace: di dalam bundel, metadata paket HTTP client-nya hilang, sehingga instrumentasinya mematikan diri tanpa sepatah kata, sampai build-nya dibuat membawa metadata itu. Histogram durasi lenyap dengan cara diam yang sama, sampai metrics pindah ke delta temporality. Service kini mencatat berapa tantangan yang dimulai, berhasil, kedaluwarsa, atau gagal, berapa lama berlangsungnya, dan kenapa pose gagal, memakai sekumpulan kecil alasan yang tetap.

Spesifikasi

MasukanToken user yang sedang login; aliran kamera langsung berupa frame JPEG dari widget
HasilPanduan untuk setiap frame; tiga tangkapan PNG di storage privat; penanda selesai; link baca untuk peninjau
TantanganDepan, kanan, kiri; tiap pose ditahan, lalu dijaga diam sebelum ditangkap; ambang menoleh bisa diatur
PemeriksaanSatu inferensi landmark wajah per frame; deteksi masker pada tangkapan hadap depan
SesiKode untuk satu user dengan umur pendek, disimpan di Redis
ObservabilityTrace, log, dan metrics OpenTelemetry lewat OTLP, termasuk hasil tantangan dan alasan pose gagal
Berjalan sebagaiExecutable hasil bundel beserta model-modelnya, dalam image container multi-stage, di samping instance Redis
Yang diterima, dikembalikan, dan dibutuhkan service.

Terkait

Service storage yang menerbitkan link tempat ketiga frame ditulis dan dibaca.

Ke mana trace, log, dan metrics service ini pergi setelah meninggalkannya.

Istilah

Liveness
Bukti bahwa orang sungguhan sedang hadir di depan kamera, bukan foto atau rekaman; ia tak mengatakan apa pun tentang siapa orangnya.
Face recognition
Mengenali atau mencocokkan seseorang dengan membandingkan wajahnya dengan wajah yang tersimpan; service ini tidak melakukannya.
Landmark wajah
Titik-titik yang ditempatkan model pada wajah — ujung hidung, tepi wajah, mata — untuk mengestimasi pose dan gerakan.
Yaw
Seberapa jauh kepala menoleh ke kiri atau ke kanan.
WebSocket
Koneksi yang tetap terbuka sehingga browser dan server bisa saling mengirim pesan terus-menerus.

Technologies covered

  • Python
  • FastAPI
  • OpenTelemetry
  • WebSocket
  • MediaPipe

About the author

Photo of Romi Fadli

Romi Fadli, S.Kom.

Solution Architect · Backend Engineer · Embedded Systems

In my day job I work as a solution architect. I design the system, lead the team that builds it — quality assurance, backend developers, a UI developer, DevOps and DevSecOps — and write the most critical modules myself, such as a certificate authority, a long-term PDF signer, and the core API of a personnel system.

I work across the whole stack that a running system actually needs. On the hardware side that means PCB design, microcontrollers, and embedded firmware — including RISC-V boards running FreeRTOS and Embassy, speaking MQTT over mutually authenticated TLS on a microcontroller with 64 KB of SRAM.

Most of my day-to-day work is software: Rust backends and API modules built on Clean Architecture, an OAuth2/OpenID Connect authorization server written from scratch, PAdES document signing, and certificate authorities — root CAs, timestamping authorities, and OCSP responders. I also build the frontends that sit on top of them.

Running all of that means infrastructure: Docker Compose stacks, networking, Mikrotik and RADIUS, observability with OpenTelemetry, and a steady habit of writing command-line tools and utilities for Linux when the existing ones do not fit.

Share

Enjoyed this?

Get an email when a new article is published.

Referenced by

Employees and administrators use the Web App, which talks to the Core API; the Core API uses storage, regions, face and preview services, sends documents to the Aurograf signing platform, sends logs and traces to the log stack, and imports from the old system

SIMPEG

Programming

Sistem informasi kepegawaian Pemerintah Kota Tangerang Selatan: pegawai mengurus record-nya sendiri dan mengikuti layanannya, admin bekerja tepat pada bagian organisasinya, dan setiap aturan berasal dari peraturan yang menetapkannya. Saya pimpin dan bangun sejak 2023.

10 min read Read more →
One definition in the middle feeds both a phone layout with stacked cards and a bottom bar, and a desktop layout with a top menu and a table

Aplikasi browser Vue dari sistem kepegawaian aparatur sipil negara: portal swalayan tempat pegawai mengurus record dan mengusulkan perubahan, serta administrasi bagan organisasi berversi, data referensi, dan akun — dibangun dari beberapa definisi bersama, bukan satu halaman per record.

9 min read Read more →
The core API at the centre, fed by storage, address, face and preview services, with documents sent across a boundary to an external signing platform

Inti Laravel sistem kepegawaian aparatur sipil negara: record pegawai dan riwayat karier, usulan swalayan yang diperiksa, keputusan kenaikan gaji berkala, kenaikan pangkat, dan pensiun, aturan dari peraturan, bagan organisasi berversi, dan akses berlingkup sampai satu unit.

11 min read Read more →
A memory chip holding a key, circled by refresh arrows, beside a browser window where a signature stamp is being dragged into place on a PDF page

Aplikasi browser Vue dari platform penandatanganan dokumen: sesi disimpan di memori dan diperbarui oleh satu refresh berantrean, login PKCE, stempel ditaruh presisi di halaman PDF, verifikasi identitas dengan tantangan wajah, persetujuan client, dan administrasi berbasis role.

10 min read Read more →
A central core holding people, organisations, documents, and certificates, connected to eight surrounding services for storage, addresses, certificates, timestamps, the root, signing, thumbnails, and face challenges

Inti Laravel dari platform penandatanganan dokumen: memegang user, identitas, organisasi, sistem client, dokumen beserta versi bertanda tangannya, stempel, dan sertifikat, serta mengorkestrasi service yang menandatangani, menstempel waktu, merender, dan menyimpan — tanpa pernah memegang file.

9 min read Read more →
A purple root circle linked to a blue and an orange intermediate circle, which lead to a signed document with a green seal and to a clock.

Aurograf

DevOps

Platform tanda tangan digital Pemerintah Kota Tangerang Selatan: penanda tangan membuktikan identitasnya, menandatangani PDF hingga PAdES B-LTA dengan sertifikat dari otoritas milik platform sendiri, dan siapa pun bisa memverifikasinya — versi 2, yang saya rancang, bangun, dan operasikan.

10 min read Read more →