Otentikasi
Panduan JWT, refresh token, API key, integration lock, WebSocket, dan header X-User-Permissions untuk integrasi dengan backend API.
Pengenalan
Backend API mendukung dua mekanisme otentikasi utama: Bearer Token (JWT) yang diperoleh dari login username/password, dan API Key untuk kebutuhan service-to-service atau automasi. Setiap request ke API harus menyertakan salah satunya. Untuk arsitektur multi-instance (federation), terdapat mekanisme tambahan berupa header X-User-Permissions yang memungkinkan Federation Module meneruskan konteks izin pengguna ke remote instance secara aman tanpa harus replay JWT user asli.
Sesi pengguna kini berbasis server (session-backed): setiap login membuat satu record sesi, access_token berumur pendek (30 menit) dan diperbarui lewat refresh_token yang dirotasi setiap kali dipakai. Selain itu, request Bearer Token dari luar dashboard dilindungi integration lock — sehingga jalur yang didukung untuk integrasi pihak ketiga adalah API Key.
Halaman ini mencakup seluruh alur otentikasi secara teknis — cara mendapatkan token, siklus hidup sesi, cara menyertakan kredensial di request HTTP maupun WebSocket, hingga alur federation antar-instance backend.
Jika Anda hanya operator yang memakai dashboard Lenz, baca Memulai. Halaman ini ditujukan untuk integrator yang memanggil API langsung dari aplikasi atau skrip mereka.
Tipe Otentikasi
| Tipe | Header yang Dipakai | Use Case |
|---|---|---|
| Bearer Token (JWT) | Authorization: Bearer <jwt> | Sesi pengguna login |
| API Key | X-Api-Key: <key> | Service-to-service / automasi / CI-CD |
| Federated | X-Api-Key: <remote_key> + X-User-Permissions: <jwt> | Federation Module → remote instance (multi-instance) |
Nama header X-Api-Key bersifat case-insensitive sesuai HTTP spec. Klien Lenz internal mengirim header dengan huruf kecil (x-api-key) — keduanya valid. Ketiga kredensial di atas juga diterima sebagai query parameter dengan nama yang sama (?authorization=, ?x-api-key=, ?x-user-permissions=) — dipakai untuk handshake WebSocket yang tidak bisa membawa header kustom. Lihat Otentikasi WebSocket.
Untuk integrator, gunakan API Key. Request dengan Authorization: Bearer dari luar dashboard Lenz wajib menyertakan X-Integration-Signature (lihat Integration Lock), sedangkan request X-Api-Key tanpa header Authorization dikecualikan dari kewajiban itu.
Jalur Mana yang Harus Saya Pakai?
Aturannya sederhana: kalau yang memanggil API bukan manusia yang sedang login di browser, pakai X-Api-Key. Bearer token hanya untuk aplikasi yang menampilkan halaman login Lenz kepada penggunanya sendiri.
| Kasus di Lenz | Contoh request |
|---|---|
| Sinkronisasi daftar kamera ke sistem inventaris / NOC | GET /api/streams |
| HRIS mendaftarkan wajah karyawan baru secara otomatis | POST /api/enrollment |
| Skrip harian mengekspor event LPR ke data warehouse | POST /api/event-history/lpr/export |
| Dashboard SOC pihak ketiga menampilkan alert real-time | wss://…/api/alert_channel?x-api-key=… |
| Sistem tiket membaca detail alert saat dibuat follow-up | GET /api/alert-history/{id} |
| Monitoring uptime memeriksa kesehatan backend | GET /api/auth/ping |
| Federation Module memanggil remote instance | X-Api-Key + X-User-Permissions (otomatis) |
curl "${API_URL}/api/streams" -H "X-Api-Key: ${API_KEY}"Tidak ada login, refresh, X-Integration-Signature, DPoP, maupun kebijakan satu sesi. Izin mengikuti role pemilik API key — buat key dari akun dengan role yang sesuai kebutuhan integrasi, bukan dari superadmin.
| Kasus di Lenz | Mengapa Bearer |
|---|---|
| Lenz Dashboard itu sendiri | Operator login dengan akun masing-masing |
| Aplikasi mobile/kiosk buatan Anda yang menampilkan form login Lenz dan meneruskan kredensial pengguna | Setiap pengguna harus melihat data sesuai role & site-nya sendiri, dan tercatat di audit log atas namanya |
curl -X POST "${API_URL}/api/auth/token" -d '{"user_access":"…","password":"…"}'
curl "${API_URL}/api/streams" -H "Authorization: Bearer ${ACCESS_TOKEN}" -H "X-Integration-Signature: ${SIG}"Konsekuensinya: wajib menyertakan X-Integration-Signature (secret hanya tersedia di server), refresh token setiap 30 menit, menangani 409 active-session-exists dan 401 session-revoked, serta DPoP jika diaktifkan admin. Jika daftar ini terasa berlebihan untuk kasus Anda, kemungkinan besar Anda seharusnya memakai API key.
Jangan campur keduanya. Mengirim X-Api-Key dan Authorization: Bearer sekaligus diperlakukan sebagai request Bearer — integration lock tetap berlaku dan API key-nya diabaikan.
Login Cepat (Untuk Operator)
Jika Anda hanya perlu masuk ke dashboard Lenz, buka browser dan kunjungi alamat Lenz yang sudah dikonfigurasi admin Anda. Masukkan username dan password pada halaman login — tidak diperlukan konfigurasi tambahan.
Login dengan Username & Password
Kirim kredensial ke /api/auth/token
Gunakan field user_access untuk username atau alamat email, dan password untuk kata sandi. Dua field opsional:
| Field | Default | Keterangan |
|---|---|---|
remember_me | false | Perpanjang masa sesi dari 1 hari menjadi 3 hari |
force_login | false | Ambil alih akun yang sedang login di tempat lain (lihat langkah berikutnya) |
curl -X POST "${API_URL}/api/auth/token" \
-H "Content-Type: application/json" \
-d '{
"user_access": "[email protected]",
"password": "your-password",
"remember_me": false
}'const res = await fetch(`${API_URL}/api/auth/token`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
user_access: "[email protected]",
password: "your-password",
}),
});
const { access_token, refresh_token, name } = await res.json();import requests
res = requests.post(
f"{API_URL}/api/auth/token",
json={"user_access": "[email protected]", "password": "your-password"},
timeout=10,
)
data = res.json()
access_token = data["access_token"]Tangani 409 jika akun sudah login di tempat lain
Lenz menerapkan kebijakan satu sesi aktif per akun (superadmin dikecualikan). Jika kredensial benar tetapi akun sudah memiliki sesi hidup, server menjawab HTTP 409 dengan informasi sesi yang sedang aktif:
{
"ok": false,
"code": "active-session-exists",
"message": "An active session already exists for this account",
"data": {
"session_count": 1,
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/126.0",
"ip_address": "10.0.4.21",
"last_used_at": "2026-09-16T08:12:44Z"
}
}Untuk mengambil alih, kirim ulang request login dengan "force_login": true. Semua sesi lama akan di-revoke dan browser lama akan langsung ter-logout pada request berikutnya (401 session-revoked).
Pengecekan ini hanya berjalan setelah password terverifikasi, sehingga respons 409 tidak bisa dipakai untuk menebak apakah suatu akun ada atau sedang login. Login via API key tidak tunduk pada kebijakan ini.
Terima respons berisi token
Jika kredensial valid, server mengembalikan HTTP 201 dengan objek JSON berikut:
{
"ok": true,
"message": "Successfull Login",
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"name": "Admin Utama"
}Simpan keduanya: access_token dipakai di setiap request dan berlaku 30 menit; refresh_token dipakai untuk memperoleh pasangan token baru tanpa login ulang (lihat Refresh Token).
Field name berisi nama tampilan pengguna. Untuk mendapatkan detail profil lengkap (role, permissions, email, default homepage), gunakan GET /api/account/profile dan GET /api/account/role setelah login.
Sertakan access_token di setiap request
Kirim token di header Authorization dengan format Bearer <token>:
curl "${API_URL}/api/streams" \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."Login dengan API Key
API key cocok untuk integrasi backend, skrip otomatis, CI/CD pipeline, atau layanan tanpa konteks pengguna interaktif. API key dibuat oleh admin di Administration → Configuration → API Key pada dashboard Lenz.

Untuk mendapatkan access_token menggunakan API key, kirim POST /api/auth/token tanpa body, namun sertakan header X-Api-Key:
curl -X POST "${API_URL}/api/auth/token" \
-H "X-Api-Key: YOUR_API_KEY_HERE"Server mengembalikan respons dalam format yang sama dengan login username/password (HTTP 201). Sesi dari login API key berlaku 7 hari (bukan 1/3 hari), tidak tunduk pada kebijakan satu sesi aktif, dan tidak mengenal remember_me.
Cara yang direkomendasikan untuk integrasi adalah memakai API key langsung di setiap request lewat header X-Api-Key, tanpa menukarnya menjadi access_token. Begitu Anda memakai Authorization: Bearer dari luar dashboard, request tersebut wajib membawa X-Integration-Signature (lihat Integration Lock) — sedangkan request X-Api-Key murni dikecualikan.
curl "${API_URL}/api/streams" \
-H "X-Api-Key: YOUR_API_KEY_HERE"Jangan menyimpan API key langsung di source code atau commit ke repository publik. Simpan di environment variable, secret manager (Vault, AWS Secrets Manager, GCP Secret Manager), atau file .env yang sudah masuk .gitignore.
Verifikasi Kredensial
Untuk memastikan token atau API key masih diterima, kirim GET /api/auth/ping. Endpoint ini ringan (tidak menyentuh data) dan menerima kedua jenis kredensial:
curl "${API_URL}/api/auth/ping" \
-H "Authorization: Bearer ${TOKEN}"
# atau
curl "${API_URL}/api/auth/ping" \
-H "X-Api-Key: ${API_KEY}"- HTTP 200 —
{ "ok": true, "message": "pong" }. Kredensial valid. - HTTP 401
not-authenticate— Token tidak valid atau kedaluwarsa → lakukan refresh. - HTTP 401
session-revoked— Sesi sudah di-revoke (logout di tempat lain, take-over, atau deteksi replay) → refresh tidak akan berhasil, login ulang.
Jika Anda juga membutuhkan profil pengguna (role, permissions, email), gunakan GET /api/account/profile — endpoint tersebut memvalidasi kredensial dengan cara yang sama.
Siklus Hidup Token & Sesi
Setiap login membuat satu sesi di server. access_token memuat ID sesi pada claim sid; bila sesi di-revoke, token tersebut ditolak meskipun belum kedaluwarsa. Tiga potongan berikut menggambarkan alur yang harus ditangani klien Bearer token.
1. Sesi yang berjalan normal
Satu sesi berisi banyak access token yang saling menggantikan lewat refresh, sampai batas waktu sesi tercapai:
Setiap refresh mengembalikan pasangan baru (access_token + refresh_token); pasangan lama hangus. Batas sesi tidak bergeser karena refresh.
2. Respons yang harus ditangani klien
| Respons | Artinya | Tindakan |
|---|---|---|
401 not-authenticate | Access token kedaluwarsa atau tidak valid | POST /api/auth/refresh, lalu ulangi request |
401 session-revoked | Sesi sudah dimatikan (lihat potongan 3) | Jangan refresh — hapus token lokal, login ulang |
401 dari /api/auth/refresh | Refresh token hangus / sesi lewat batas | Login ulang |
409 active-session-exists | Akun sedang login di tempat lain | Konfirmasi ke pengguna → login ulang dengan force_login: true |
403 integration_lock | Bearer tanpa X-Integration-Signature valid | Beralih ke X-Api-Key (integrasi) atau perbaiki signature |
3. Apa yang mengakhiri sesi
Replay paling sering terjadi bukan karena pencurian, melainkan dua proses di klien yang sama-sama memakai refresh_token lama secara bersamaan. Akibatnya seluruh sesi pengguna di-revoke. Pastikan refresh berjalan single-flight.
access_token | refresh_token | |
|---|---|---|
| Fungsi | Mengotorisasi request API | Memperoleh pasangan token baru |
| Masa berlaku | 30 menit | Mengikuti sesi: 1 hari (default), 3 hari (remember_me), 7 hari (login via API key) |
| Sifat | Stateless JWT dengan claim sid | Sekali pakai — dirotasi pada setiap refresh |
| Bila kedaluwarsa | 401 not-authenticate → refresh | Login ulang |
Admin dapat mengubah durasi lewat environment variable gateway (sintaks durasi Go, mis. 45s, 12h):
| Env var | Default | Mengatur |
|---|---|---|
ACCESS_TOKEN_TTL | 30m | Masa berlaku access token |
SESSION_TOKEN_TTL | 24h | Sesi login username/password tanpa remember_me |
REMEMBER_ME_TOKEN_TTL | 72h | Sesi login dengan remember_me: true |
REFRESH_TOKEN_TTL | 168h | Sesi login via API key |
Selain batas di atas, dashboard Lenz juga menerapkan idle timeout di sisi klien (dikonfigurasi admin di Preferences). Idle timeout memicu logout eksplisit — bukan mekanisme server — sehingga tidak berpengaruh pada integrasi API.
Refresh Token
Ketika access_token kedaluwarsa (401 not-authenticate), tukarkan refresh_token dengan pasangan token baru lewat POST /api/auth/refresh. Endpoint ini tidak memerlukan header Authorization.
curl -X POST "${API_URL}/api/auth/refresh" \
-H "Content-Type: application/json" \
-d '{ "refresh_token": "'"${REFRESH_TOKEN}"'" }'{
"ok": true,
"message": "Token refreshed",
"access_token": "eyJ...baru...",
"refresh_token": "eyJ...baru...",
"name": "Admin Utama"
}Aturan yang perlu diperhatikan:
- Rotasi — refresh token lama langsung tidak berlaku; selalu simpan pasangan yang baru dikembalikan.
- Tidak memperpanjang sesi — pasangan baru mewarisi batas waktu sesi semula (1/3/7 hari). Setelah itu, login ulang.
- Deteksi replay — mengirim refresh token yang sudah pernah dirotasi dianggap sebagai token bocor dan merevoke seluruh sesi pengguna tersebut (superadmin: hanya sesi yang bersangkutan). Refresh token yang di-revoke lewat logout/take-over hanya ditolak.
- Single-flight — jika aplikasi Anda menjalankan request paralel, pastikan hanya satu proses refresh berjalan pada satu waktu dan request lain menunggu hasilnya; dua refresh bersamaan dengan token yang sama akan memicu deteksi replay di atas.
Logout
Untuk mengakhiri sesi di server, kirim POST /api/auth/logout dengan refresh_token pada body. Endpoint ini tidak memerlukan header Authorization — dengan begitu logout tetap bisa dilakukan meski access token sudah kedaluwarsa.
curl -X POST "${API_URL}/api/auth/logout" \
-H "Content-Type: application/json" \
-d '{ "refresh_token": "'"${REFRESH_TOKEN}"'" }'Setelah logout, refresh token tersebut dan access token yang terikat pada sesi yang sama ditolak (401 session-revoked). Endpoint ini idempoten — token yang sudah di-revoke, kedaluwarsa, tidak dikenal, atau body kosong tetap dijawab 200.
Integration Lock (X-Integration-Signature)
Route privat /api/* dilindungi integration lock: request yang membawa Authorization: Bearer harus menyertakan header X-Integration-Signature berisi JWT HS256 pendek yang ditandatangani dengan secret per-deployment. Dashboard Lenz melakukannya otomatis. Tujuannya menutup penyalahgunaan API memakai kredensial login pengguna yang "dipinjam" dari browser.
| Pemanggil | Perlu X-Integration-Signature? |
|---|---|
Integrasi API key (X-Api-Key, tanpa header Authorization) | Tidak — inilah jalur yang didukung |
Dashboard / sesi pengguna (Authorization: Bearer) | Ya — otomatis oleh dashboard |
| Skrip yang memakai Bearer token pengguna | Ya — inilah yang dicegah |
Federation (X-Api-Key + X-User-Permissions, tanpa Bearer) | Tidak |
Route publik (login, refresh, logout, auth/status, version, ping, channel WebSocket, image reads) | Tidak |
Request Bearer tanpa signature yang valid dijawab HTTP 403:
{ "ok": false, "code": "integration_lock", "message": "integration signature required" }Secret integration lock tidak dapat diperoleh lewat API — ia dibuat ulang setiap container Lenz dijalankan dan hanya bisa dibaca oleh dashboard atau operator dengan akses shell ke server. Karena itu, jangan membangun integrasi di atas Bearer token pengguna; gunakan API key per request. Jika Anda menukar API key menjadi access_token lalu memakainya sebagai Bearer, request Anda kembali terkena kewajiban signature.
Untuk memeriksa apakah signature yang Anda pegang masih sinkron (mis. setelah redeploy), gunakan endpoint publik GET /api/auth/status — ia selalu menjawab 200 dengan flag integration: true|false dan tidak pernah membocorkan secret.
Otentikasi WebSocket
Browser tidak bisa mengirim header kustom pada handshake WebSocket, sehingga middleware otentikasi menerima kredensial yang sama melalui query parameter: authorization, x-api-key, dan x-user-permissions. Nilai authorization boleh berupa token mentah maupun berawalan Bearer . Channel WebSocket juga dikecualikan dari integration lock.
| Channel | Endpoint | Otentikasi |
|---|---|---|
| Event Channel | /api/event_channel | Tidak ada — publik, difilter lewat query (stream_ids, analytic_id, instance, …) |
| System Channel | /api/system_channel | Tidak ada — publik |
| Alert Channel | /api/alert_channel | Wajib: ?authorization=<access_token> atau ?x-api-key=<key> |
const url = new URL(`wss://${BASE_URL}/api/alert_channel`);
url.searchParams.set('x-api-key', API_KEY);
url.searchParams.set('disable_base64_image', 'true');
const ws = new WebSocket(url.toString());
ws.onmessage = (e) => console.log(JSON.parse(e.data));const url = new URL(`wss://${BASE_URL}/api/alert_channel`);
url.searchParams.set('authorization', accessToken); // tanpa awalan "Bearer " juga diterima
url.searchParams.set('disable_base64_image', 'false');
const ws = new WebSocket(url.toString());Access token hanya berlaku 30 menit. Handshake yang sudah berhasil tidak diputus saat token kedaluwarsa, tetapi reconnect membutuhkan token yang masih valid — refresh token Anda sebelum menyambung ulang.
import WebSocket from 'ws';
const ws = new WebSocket(`wss://${BASE_URL}/api/alert_channel`, {
headers: { 'X-Api-Key': API_KEY },
});Token pada query string dapat tercatat di log proxy/akses. Untuk klien non-browser, kirim kredensial lewat header. Untuk browser, pakai API key dengan scope minimal, atau access token berumur pendek.
Detail parameter filter dan format payload setiap channel dibahas di Events — WebSocket Protocol.
DPoP (Opsional)
Backend mendukung DPoP (RFC 9449, sender-constrained tokens): access/refresh token diikat ke kunci privat non-extractable di browser, sehingga token yang bocor tidak berguna tanpa proof dari browser asal. Fitur ini dikendalikan admin lewat env DPOP_MODE (off — default, monitor, require) dan hanya memengaruhi jalur Authorization: Bearer. Login via API key dan request federated tidak pernah terikat DPoP.
Jika deployment Anda menjalankan DPOP_MODE=require, klien kustom yang memakai Bearer token harus mengimplementasikan proof DPoP (header DPoP, nonce dari header DPoP-Nonce, respons 401 use-dpop-nonce / invalid-dpop-proof). Ini adalah alasan tambahan mengapa integrasi pihak ketiga sebaiknya memakai API key.
Header Federation: X-User-Permissions
Dalam arsitektur multi-instance Lenz, ada satu Federation Module (Core) yang berperan sebagai aggregator, dan satu atau lebih remote instance (instance backend tambahan). Saat Federation Module meneruskan request pengguna ke remote instance, ia tidak menggunakan Bearer Token pengguna secara langsung. Sebagai gantinya, Federation Module:
- Mengautentikasi dirinya ke remote instance menggunakan API key remote instance (
X-Api-Key). - Mengemas informasi pengguna dan izin yang sudah difilter ke dalam JWT baru.
- Menandatangani JWT tersebut menggunakan API key remote instance sebagai HMAC secret.
- Menyertakan JWT tersebut di header
X-User-Permissions.
Remote instance kemudian memvalidasi tanda tangan JWT menggunakan API key-nya sendiri, mengekstrak izin pengguna, dan menjalankan access control berbasis data JWT — tanpa perlu lookup role ke database lokalnya.
Struktur Payload JWT X-User-Permissions
{
"sub": "federer:123",
"email": "[email protected]",
"role_id": "operator-role",
"role_name": "operator-role",
"permissions": {
"[remote]core": {
"site_id|1": ["stream-abc", "stream-def"],
"site_id|2": ["*"],
"no_site": ["stream-xyz"]
}
},
"iat": 1705123456,
"exp": 1705123756
}| Field | Deskripsi |
|---|---|
sub | Subject identifier dalam format "federer:{user_id}" |
email | Alamat email pengguna |
role_id / role_name | Nama role (bukan ID database) |
permissions | Izin pengguna yang sudah difilter untuk remote instance target, dengan key diubah menjadi [remote]core |
iat / exp | Timestamp issued-at dan expiration (TTL 5 menit) |
Header X-User-Permissions hanya boleh dikirim oleh Federation Module yang sudah terdaftar dan memiliki API key remote instance yang valid. Jangan gunakan header ini untuk integrasi pihak ketiga — request semacam itu akan ditolak karena JWT tidak akan bisa divalidasi tanpa API key yang tepat sebagai secret HMAC.
Skenario Tanpa API Key Remote Instance (Mode Internal)
Jika Federation Module dijalankan satu cluster dengan remote instance dan tidak ingin pakai API key, secret JWT default PERMISSIONS_JWT_SECRET (env var) digunakan oleh kedua sisi. Mode ini tidak direkomendasikan untuk produksi karena tidak memberi isolasi per instance — bocornya secret di satu tempat berarti seluruh cluster terkompromi.
Pesan Error Umum
Respons error otentikasi selalu menyertakan field code yang bisa dibaca mesin — gunakan code, bukan message, untuk percabangan logika di klien.
Tips Integrasi
Best practice untuk integrator:
- Integrasi server-to-server: pakai
X-Api-Keyper request tanpa headerAuthorization. Tidak ada login, refresh, integration lock, DPoP, maupun kebijakan satu sesi yang perlu dipikirkan. - Jika Anda tetap memakai Bearer token: simpan
access_tokendanrefresh_tokendi memori atau secure storage, implementasikan handler401yang membedakannot-authenticate(→ refresh, lalu ulangi request) darisession-revoked(→ login ulang), dan pastikan refresh berjalan single-flight. - Gunakan API key terpisah per integrasi, dan rotasi berkala sesuai kebijakan keamanan organisasi Anda.
- Untuk WebSocket dari browser, kirim kredensial lewat query parameter (
x-api-keyatauauthorization); dari klien non-browser, lebih aman lewat header. - Untuk federated requests, perhatikan TTL JWT
X-User-Permissionsyang hanya 5 menit — Federation Module membuat JWT baru untuk setiap batch request. - Jangan hardcode token atau API key di kode sumber. Gunakan environment variable atau secret manager.
- Saat federation aktif, sertakan parameter
instancedi setiap request ke endpoint yang scope-nya per-instance, agar data tidak tercampur.
Selanjutnya
API Reference — Otentikasi
Schema lengkap login, refresh, logout, ping, status, dan manajemen API key (OpenAPI).
Events — WebSocket Protocol
Parameter filter dan format payload event_channel / alert_channel.
Konsep Dasar — Keamanan & Akses
Penjelasan RBAC, role, dan permission untuk operator.
Memulai
Panduan login dan navigasi awal untuk operator dashboard.