# X-Guard: dokumentasi lengkap Indeks: https://docs.x-guard.online/llms.txt # Mulai cepat Sumber: https://docs.x-guard.online/mulai # Mulai cepat X-Guard adalah **OpenID Provider per organisasi**. Setiap organisasi punya alamat sendiri (issuer), mis. `https://nama-organisasi.x-guard.online`, atau domain sendiri. Aplikasi menitipkan pengguna ke halaman login organisasi itu, lalu menerima identitasnya lewat OpenID Connect standar. ```text Browser Aplikasi Anda X-Guard organisasi | klik Masuk | | |-------------------->| /auth/login | |<--------------------| alihkan ke /authorize (PKCE) | |------------------------------------------------------>| login (sekali per sesi) |<------------------------------------------------------| kembali ke /auth/callback?code=... |-------------------->| tukar code (server ke server) | | |------------------------------>| | |<------------------------------| ID token (JWT RS256) |<--------------------| sesi aplikasi dibuat | ``` ## 1. Daftarkan aplikasi Admin organisasi membuka konsol X-Guard → **Aplikasi** → **Tambah aplikasi**, mengisi alamat callback dan alamat sesudah logout, lalu menyimpan **client ID** dan **client secret** (secret hanya tampil sekali). Rinciannya di [Mendaftarkan aplikasi](/panduan/aplikasi). ## 2. Catat alamat X-Guard Rincian aplikasi di konsol menampilkan **issuer** dan panel **Panduan integrasi** berisi konfigurasi siap salin untuk Laravel, Go, ASP Classic, dan Node.js, sudah terisi nilai aplikasi Anda. ## 3. Pilih SDK atau pustaka OIDC | Aplikasi | Pakai | |---|---| | Laravel 10 sampai 12 | [SDK Laravel](/sdk/laravel) | | Go (net/http, Gin) | [SDK Go](/sdk/go) | | ASP Classic (IIS) | [SDK ASP Classic](/sdk/asp-classic), tanpa DLL atau komponen tambahan | | Node.js | [openid-client](/sdk/node) | | Lainnya | Pustaka OpenID Connect yang membaca discovery dan mendukung PKCE | ## 4. Isi konfigurasi Simpan di konfigurasi server (env), **bukan** di kode browser atau repositori: ```dotenv XGUARD_ISSUER=https://nama-organisasi.x-guard.online XGUARD_CLIENT_ID=... XGUARD_CLIENT_SECRET=... XGUARD_REDIRECT_URI=https://aplikasi-anda/auth/callback XGUARD_POST_LOGOUT_URI=https://aplikasi-anda/ ``` ## 5. Uji 1. Klik **Masuk dengan X-Guard**, login, dan pastikan kembali ke aplikasi sebagai pengguna itu. 2. Buka aplikasi organisasi yang lain: langsung masuk tanpa password. 3. Logout di satu aplikasi: aplikasi lain ikut keluar (lewat back-channel logout atau pemeriksaan sesi berkala). Sebelum ke produksi, periksa [daftar periksa produksi](/panduan/produksi). --- # Brief untuk agen AI Sumber: https://docs.x-guard.online/agen # Brief untuk agen AI Halaman ini untuk agen AI (atau developer) yang ditugasi menyambungkan sebuah aplikasi ke SSO X-Guard. Isinya cukup untuk merancang, mengerjakan, dan menguji integrasi tanpa bertanya ulang. Versi Markdown halaman ini: `https://docs.x-guard.online/agen.md`; semua halaman sekaligus: `https://docs.x-guard.online/llms-full.txt`. ## 1. Tentang X-Guard - OpenID Provider **per organisasi**. Issuer: `https://.x-guard.online`, atau domain sendiri organisasi. Discovery: `/.well-known/openid-configuration`. - Alur: authorization code + **PKCE S256**, `state`, `nonce`. ID token dan access token JWT **RS256** (JWKS di `/keys`). - Scope: `openid profile email` (tambah `offline_access` untuk refresh token). - Klaim ID token: `sub` (ID tetap, diawali `USR_`), `name`, `email`, `email_verified`, `sid`, `auth_time`, `amr`. Access token (5 menit) menambah `client_id`, `scp`, `org`. - **Tidak ada klaim role.** Hak akses tetap diatur aplikasi. ## 2. Keputusan bawaan Ikuti kecuali pengguna memutuskan lain. 1. Tombol **Masuk dengan X-Guard**. Login lama tetap ada selama masa transisi; jangan dihapus tanpa persetujuan. 2. **Penautan akun**: tambah kolom `xguard_sub` (teks 64, unik, boleh kosong). Cari lewat `xguard_sub`; login pertama cari lewat email **hanya bila `email_verified`** lalu isi `xguard_sub`; tetap tidak ketemu → tolak, atau buat akun dengan hak **paling rendah** bila pengguna memintanya. 3. Sesudah identitas lolos, tetap lewat gerbang aplikasi sendiri (status akun, role, maintenance). 4. Aplikasi menerbitkan sesinya sendiri. Simpan `sid` dan `id_token` di sesi (untuk back-channel dan logout). Ganti ID sesi sesudah login. 5. Logout: hapus sesi lokal, lalu alihkan ke `end_session` dengan `id_token_hint` dan `post_logout_redirect_uri`. 6. Aplikasi dengan alamat publik: pasang **back-channel logout**. Tanpa alamat publik (intranet, localhost): pasang **pemeriksaan sesi senyap** (`prompt=none`) tiap 1 sampai 15 menit pada GET halaman. 7. Pasang **webhook** bila aplikasi menyimpan akun lokal: `user.disabled`, `user.deleted`, dan `user.apps_changed` dengan `access` `revoked` mencabut semua sesi pengguna itu. 8. Pakai SDK X-Guard bila stack-nya tersedia; selain itu pustaka OIDC tersertifikasi. Jangan menulis kriptografi sendiri. ## 3. Data yang diminta ke pengguna | Data | Contoh | |---|---| | Issuer organisasi | `https://acme.x-guard.online` (tampil di rincian aplikasi di konsol) | | Alamat dasar aplikasi per lingkungan | `https://hr.acme.co.id`, `http://localhost:8000` | | Path callback, sesudah logout, back-channel, webhook | `/auth/callback`, `/`, `/auth/backchannel-logout`, `/auth/webhook` | | Nama variabel env yang dipakai aplikasi | `XGUARD_ISSUER`, `XGUARD_CLIENT_ID`, ... | Pengguna (admin organisasi) mendaftarkan aplikasi di konsol X-Guard → **Aplikasi** → **Tambah aplikasi**, dengan alamat di atas, lalu menaruh **client ID**, **client secret**, dan (bila webhook dipakai) **secret webhook** langsung di env server. **Jangan pernah meminta secret dikirim lewat chat**, dan jangan menulisnya ke repositori atau log. Cukup minta pengguna mengisi env, lalu kode membaca dari sana. ## 4. Langkah per stack ### Laravel 10 sampai 12 Ambil berkas dari [SDK Laravel](/sdk/laravel) (`/berkas/laravel/...`), `composer require firebase/php-jwt`, daftarkan `XGuardSso` sebagai singleton, pasang rute `/auth/*`, kecualikan `auth/backchannel-logout` dan `auth/webhook` dari CSRF, jalankan migrasi `xguard_sub`, pasang middleware `auth` + `xguard.sesi`. ### Go Ambil [`/berkas/go/xguard/`](/sdk/go) ke `internal/xguard`. `xguard.New`, `StartLogin`, `HandleCallback`, `BackchannelLogoutHandler`, `WebhookHandler`, `RequireAccessToken`. ### ASP Classic Ambil folder [`/berkas/asp-classic/sso/`](/sdk/asp-classic) ke root situs. Yang disesuaikan hanya `xg_config.asp` (nilai dari konsol) dan `xg_aplikasi.asp` (database dan nama Session aplikasi). Buka `cek-koneksi.asp` dulu; semua baris harus `OK`. Jangan mengubah `xg_lib.asp`. ### Node.js `openid-client` 6, contoh lengkap di [Node.js](/sdk/node). ### Next.js dengan Auth.js Provider OIDC generik: ```ts { id: "xguard", name: "X-Guard", type: "oidc", issuer: process.env.XGUARD_ISSUER, clientId: process.env.XGUARD_CLIENT_ID, clientSecret: process.env.XGUARD_CLIENT_SECRET, checks: ["pkce", "state", "nonce"], authorization: { params: { scope: "openid profile email" } } } ``` Alamat callback-nya `https:///api/auth/callback/xguard`. Kirim `access_token` ke API backend; backend Go memakai `RequireAccessToken`. ### Lainnya (Java, .NET, Python, Ruby) Pustaka OIDC yang membaca discovery: Spring Security OAuth2 Client, ASP.NET Core `AddOpenIdConnect`, Authlib, OmniAuth OpenID Connect. Pastikan PKCE aktif, `ResponseType` = `code`, dan scope `openid profile email`. ## 5. Kontrak yang harus dipatuhi - **Alamat callback dicocokkan persis.** `https` wajib, kecuali `localhost`. Satu karakter beda = ditolak. - **Galat callback**: `access_denied` (tidak diberi akses: tampilkan pesan, jangan ulang), `login_required` (login senyap tanpa sesi: akhiri sesi lokal, lalu login biasa). Percayai galat hanya bila `state` cocok. - **Back-channel logout**: `POST` form `logout_token` (JWT RS256: `iss`, `aud` = client ID, `sid`, `events` berisi `http://schemas.openid.net/event/backchannel-logout`, tanpa `nonce`). Cabut sesi ber-`sid` sama, jawab 200. - **Webhook**: `POST` JSON `{"id","type","sub","email","occurred_at"}` (+ `access` untuk `user.apps_changed`), header `X-XGuard-Timestamp` dan `X-XGuard-Signature: sha256=`. Tolak selisih waktu lebih dari 5 menit. Jawab 2xx untuk event yang sudah diproses, `id` ganda, atau `sub` yang tidak dikenal. Selain 2xx dicoba ulang sampai 24 jam dengan `id` dan body yang sama; urutan dijaga per pengguna. - **Refresh token** dirotasi: simpan yang baru setiap kali. Pemakaian ulang mencabut seluruh rangkaian. ## 6. Jebakan yang pernah terjadi - **Cookie state login hilang di BFF atau proxy.** Bila Next.js meneruskan `/api` ke backend, cookie sementara login (state, nonce, verifier) harus diteruskan dua arah untuk rute `/auth/*`. Gejalanya: login di X-Guard berhasil, tetapi callback selalu "state tidak cocok". - **Login dimulai dari host berbeda dengan callback** (tanpa `www` lalu kembali ke `www`, atau `localhost` lalu `127.0.0.1`): cookie sesi tidak terbawa, callback ditolak. Pakai satu alamat. - **Body webhook di-parse lalu diserialisasi ulang** sebelum HMAC: tanda tangan tidak pernah cocok. Pakai body mentah, dan teruskan header `X-XGuard-*` apa adanya di proxy. - **Dua aplikasi di `localhost` beda port** berbagi cookie (cookie tidak dibedakan per port): beri nama cookie sesi berbeda. - **Jam server melenceng**: ID token ditolak sebagai kedaluwarsa atau dari masa depan. - **Pemeriksaan sertifikat dimatikan** untuk mengatasi galat HTTPS (terutama di ASP Classic dan Windows lama). Jangan: perbaiki TLS-nya. - **Back-channel logout ke `localhost`** tidak pernah sampai. Pakai pemeriksaan sesi senyap untuk pengembangan. ## 7. Uji 1. **Login**: Masuk dengan X-Guard → login → kembali ke aplikasi. Kolom `xguard_sub` terisi; `sid` tersimpan di sesi. 2. **SSO**: buka aplikasi organisasi lain → masuk tanpa password, `sid` sama. 3. **Logout dari aplikasi**: sesi lokal dan sesi X-Guard berakhir; kembali ke alamat sesudah logout. 4. **Logout dari aplikasi lain**: sesi di aplikasi ini berakhir (back-channel seketika, atau pada pemeriksaan senyap). 5. **Akses dicabut** di konsol (Akses pengguna → hanya pengguna terpilih, tanpa akun uji): login ditolak dengan pesan akses. 6. **Webhook**: **Uji kirim** di konsol → riwayat **Terkirim**. Nonaktifkan akun uji → sesinya berakhir. 7. **Keamanan**: `/auth/callback?code=x&state=palsu` ditolak; `next=https://situs-lain` berakhir di halaman aplikasi sendiri. Sebelum produksi: [daftar periksa](/panduan/produksi). --- # Mendaftarkan aplikasi Sumber: https://docs.x-guard.online/panduan/aplikasi # Mendaftarkan aplikasi Admin organisasi membuka konsol X-Guard → **Aplikasi** → **Tambah aplikasi**. | Isian | Keterangan | |---|---| | Nama | Tampil di halaman login organisasi | | Jenis | **Web**: aplikasi dengan server, memakai client secret. **SPA**: aplikasi di browser. **Native**: aplikasi ponsel atau desktop. SPA dan native tanpa secret dan wajib PKCE | | Alamat callback | Tujuan X-Guard mengembalikan pengguna sesudah login. Dicocokkan **persis** (skema, host, port, path). Wajib `https`, kecuali `http://localhost` atau `127.0.0.1` untuk pengembangan | | Alamat sesudah logout | Tujuan sesudah logout, juga dicocokkan persis | | Alamat back-channel logout | Opsional, aplikasi Web dengan alamat publik: X-Guard memanggilnya antarserver saat sesi berakhir ([Logout dan sesi](/panduan/logout)) | | Autentikasi di endpoint token | `client_secret_basic` (bawaan) atau `client_secret_post`. X-Guard menerima keduanya | | Scope tambahan | Opsional: scope API sendiri, di luar `openid`, `profile`, `email`, `offline_access` | | Audience | Opsional: client ID API lain yang boleh diminta lewat parameter `audience` | ## Client secret Secret tampil **sekali** sesudah aplikasi dibuat atau secret diganti. Simpan di konfigurasi server (env, secret manager), jangan di repositori, kode browser, chat, atau email biasa. Bila hilang atau bocor, tekan **Ganti secret** di rincian aplikasi; secret lama langsung tidak berlaku. ## Akses pengguna Panel **Akses pengguna** di rincian aplikasi menentukan siapa yang boleh masuk: - **Semua pengguna organisasi** (bawaan). - **Hanya pengguna terpilih**: pengguna lain ditolak dengan `access_denied` ke alamat callback, dan refresh token mereka berhenti bekerja. ## Status aplikasi Aplikasi yang **dinonaktifkan** organisasi, atau **ditangguhkan** tim X-Guard, tidak bisa dipakai login sampai diaktifkan lagi. ## Panduan integrasi di konsol Rincian aplikasi menampilkan issuer, semua alamat X-Guard, dan konfigurasi siap salin untuk Laravel, Go, ASP Classic, dan Node.js yang sudah terisi client ID dan alamat aplikasi Anda. --- # Login Sumber: https://docs.x-guard.online/panduan/login # Login ## Alamat Issuer: `https://.x-guard.online`, atau domain sendiri bila organisasi memakainya. Semua alamat lain dibaca dari discovery `/.well-known/openid-configuration`; tulis tetap hanya bila pustaka Anda tidak membaca discovery (mis. ASP Classic). | Endpoint | Alamat | |---|---| | Discovery | `/.well-known/openid-configuration` | | Authorize | `/authorize` | | Token | `/oauth/token` | | Userinfo | `/userinfo` | | Kunci publik (JWKS) | `/keys` | | Logout | `/end_session` | | Introspeksi | `/oauth/introspect` | | Pencabutan token | `/revoke` | Issuer adalah identitas organisasi: token dari issuer lain (organisasi lain) harus ditolak. ## Alur Authorization code dengan **PKCE S256**, `state`, dan `nonce`: 1. Aplikasi membuat `state`, `nonce`, dan `code_verifier` acak, menyimpannya di sesi server (atau cookie bertanda tangan), lalu mengalihkan browser ke `/authorize` dengan `response_type=code`, `client_id`, `redirect_uri`, `scope`, `state`, `nonce`, `code_challenge`, dan `code_challenge_method=S256`. 2. Pengguna login di halaman organisasi. Bila sesi X-Guard masih ada, langkah ini dilewati. 3. X-Guard mengalihkan kembali ke `redirect_uri?code=...&state=...`. 4. Aplikasi mencocokkan `state`, lalu menukar `code` di `/oauth/token` dari server, dengan client secret (Basic auth atau body) dan `code_verifier`. 5. Aplikasi memverifikasi ID token (lihat [Token dan klaim](/panduan/token)), lalu membuat sesinya sendiri. ## Scope | Scope | Isi | |---|---| | `openid` | Wajib. Klaim `sub` | | `profile` | `name` | | `email` | `email`, `email_verified` | | `offline_access` | Refresh token | ## Parameter tambahan | Parameter | Arti | |---|---| | `prompt=none` | Login senyap: tanpa layar apa pun. Bila sesi X-Guard tidak ada, dijawab `login_required` | | `prompt=login` | Paksa login ulang walau sesi masih ada | | `max_age=` | Sesi yang lebih tua dari batas ini dianggap tidak ada | | `audience=` | Minta access token untuk API lain (harus diizinkan di kolom Audience aplikasi) | ## Galat di alamat callback X-Guard mengalihkan ke `redirect_uri?error=...&error_description=...&state=...`. Percayai galat **hanya** bila `state` cocok dengan yang disimpan, supaya pihak luar tidak bisa memaksa aplikasi ke keadaan tertentu. | `error` | Artinya | Yang dilakukan aplikasi | |---|---|---| | `access_denied` | Pengguna tidak diberi akses ke aplikasi ini | Tampilkan pesan; jangan diulang otomatis | | `login_required` | Login senyap, tetapi sesi X-Guard tidak ada (sudah logout, kedaluwarsa, atau akun dinonaktifkan) | Akhiri sesi lokal, lalu login biasa | | `interaction_required`, `consent_required` | Login senyap tidak bisa diselesaikan tanpa pengguna | Perlakukan seperti `login_required` | | `invalid_request` | Permintaan salah, mis. SPA tanpa PKCE | Perbaiki konfigurasi aplikasi | Alamat callback atau `client_id` yang salah tidak dialihkan ke aplikasi: X-Guard menampilkan halaman galat sendiri, karena mengalihkan ke alamat yang tidak terdaftar berbahaya. --- # Token dan klaim Sumber: https://docs.x-guard.online/panduan/token # Token dan klaim ## ID token JWT bertanda tangan **RS256**. Kunci publik di `/keys`; kunci dirotasi berkala, jadi ambil ulang JWKS bila `kid` belum dikenal. Wajib diperiksa: - tanda tangan dengan JWKS issuer organisasi, - `iss` sama persis dengan issuer, - `aud` berisi client ID aplikasi, - `exp` belum lewat (toleransi jam beberapa detik sampai 2 menit), - `nonce` sama dengan yang dikirim saat login. | Klaim | Isi | |---|---| | `sub` | ID pengguna (diawali `USR_`). **Tidak pernah berubah**, walau email berganti: pakai sebagai kunci akun lokal | | `name` | Nama lengkap (scope `profile`) | | `email`, `email_verified` | Email utama dan status verifikasinya (scope `email`) | | `sid` | Sesi X-Guard. Simpan di sesi aplikasi untuk back-channel logout | | `auth_time` | Waktu pengguna terakhir benar-benar login (bukan waktu token diterbitkan) | | `amr` | Cara login, mis. `["pwd"]` | | `iss`, `aud`, `exp`, `iat`, `nonce`, `at_hash` | Standar OpenID Connect | X-Guard **tidak** mengirim role atau jabatan. Hak akses di aplikasi tetap urusan aplikasi. ## Menautkan akun 1. Cari pengguna lokal lewat `sub` yang tersimpan (mis. kolom `xguard_sub`). 2. Login pertama: cari lewat email **hanya bila `email_verified` bernilai true**, lalu simpan `sub`-nya. 3. Tetap tidak ketemu: buat akun dengan hak paling rendah, atau tolak, sesuai kebijakan aplikasi. ## Access token JWT RS256 berumur **5 menit**. Kirim ke API Anda sebagai `Authorization: Bearer `. | Klaim | Isi | |---|---| | `sub` | ID pengguna | | `client_id` | Aplikasi yang meminta token | | `aud` | Aplikasi peminta dan API tujuan yang diminta lewat `audience` | | `scp` | Scope yang diberikan | | `org` | Slug organisasi pengguna | | `iss`, `exp`, `iat`, `jti` | Standar | API yang menerima access token memeriksa tanda tangan, `iss`, `exp`, dan `client_id` atau `aud` miliknya. API yang melayani banyak organisasi juga wajib memeriksa `iss` dan `org`. ## Refresh token Diterbitkan bila scope `offline_access` diminta. Berlaku **30 hari** dan **dirotasi** setiap kali dipakai: simpan refresh token baru dari setiap jawaban. Memakai refresh token yang sudah diputar mencabut seluruh rangkaiannya (dianggap bocor). Refresh ditolak bila akses pengguna ke aplikasi dicabut, akunnya dinonaktifkan atau dihapus, atau pengguna logout dari sesi X-Guard. Aplikasi lalu meminta pengguna login lagi. ## Userinfo dan introspeksi `/userinfo` dengan access token mengembalikan klaim profil yang sama dengan ID token. `/oauth/introspect` (dengan client secret) memeriksa apakah sebuah token masih aktif. --- # Logout dan sesi Sumber: https://docs.x-guard.online/panduan/logout # Logout dan sesi Satu sesi X-Guard dipakai semua aplikasi organisasi. Logout mengakhiri sesi itu, jadi semua aplikasi ikut keluar. ## Logout dari aplikasi 1. Hapus sesi lokal aplikasi. 2. Alihkan browser ke: ```text /end_session?id_token_hint=&post_logout_redirect_uri= ``` `id_token_hint` (ID token dari login) membuktikan permintaan berasal dari aplikasi Anda. X-Guard mengakhiri sesi, memberi tahu aplikasi lain lewat back-channel logout, lalu mengembalikan pengguna ke `post_logout_redirect_uri` (yang harus terdaftar). Tanpa `id_token_hint`, X-Guard meminta konfirmasi pengguna lebih dulu. ## Back-channel logout Untuk aplikasi Web yang alamatnya bisa dijangkau dari internet. Isi **Alamat back-channel logout** di konsol, mis. `https://aplikasi-anda/auth/backchannel-logout`. Saat sesi X-Guard berakhir (dari aplikasi mana pun), X-Guard mengirim: ```http POST /auth/backchannel-logout Content-Type: application/x-www-form-urlencoded logout_token= ``` Periksa `logout_token`: - tanda tangan RS256 dengan JWKS issuer, `iss` sama dengan issuer, `aud` berisi client ID, - klaim `events` berisi `http://schemas.openid.net/event/backchannel-logout`, - **tidak** ada klaim `nonce`, - `iat` tidak lebih dari beberapa menit lalu. Lalu cabut sesi lokal yang `sid`-nya sama dengan klaim `sid` (simpan `sid` dari ID token saat login), dan jawab `200`. Bila `sid` kosong, cabut semua sesi milik `sub`. ## Aplikasi tanpa alamat publik Aplikasi intranet atau `localhost` tidak bisa dijangkau X-Guard. Pakai **login senyap berkala**: setiap beberapa menit (mis. 1 sampai 15 menit) pada permintaan halaman GET, alihkan ke `/authorize` dengan `prompt=none`. - Sesi X-Guard masih ada: X-Guard langsung mengembalikan `code`, pengguna tidak melihat apa pun. - Sesi sudah berakhir (logout di aplikasi lain, kedaluwarsa, akun dinonaktifkan): `error=login_required`. Akhiri sesi lokal dan minta login lagi. - Akses ke aplikasi dicabut: `error=access_denied`. Akhiri sesi lokal. SDK Laravel dan ASP Classic sudah menyediakan pemeriksaan ini (`CekSesiXGuard`, `xgWajibLogin`). ## Pengguna dinonaktifkan Saat admin menonaktifkan atau menghapus pengguna, sesi X-Guard-nya tidak bisa dipakai lagi dan refresh token-nya ditolak. Access token yang sudah terbit habis dalam 5 menit. Supaya sesi lokal ikut berakhir seketika, pasang [webhook](/panduan/webhook) dan tangani `user.disabled` dan `user.deleted`. --- # Webhook Sumber: https://docs.x-guard.online/panduan/webhook # Webhook X-Guard memberi tahu server aplikasi saat data pengguna organisasi berubah. Pasang di rincian aplikasi → panel **Webhook**: satu alamat https per aplikasi, pilih event, lalu simpan **secret webhook** (diawali `xgwh_`, tampil sekali). Event pengguna hanya dikirim ke aplikasi yang boleh dibuka pengguna itu. ## Event | Event | Artinya | Yang biasanya dilakukan | |---|---|---| | `user.created` | Akun aktif pertama kali, termasuk undangan yang diterima | Siapkan akun lokal (opsional) | | `user.updated` | Nama atau email berubah | Perbarui profil lokal | | `user.disabled` | Ditangguhkan atau dinonaktifkan admin | Tangguhkan akun lokal dan cabut semua sesinya | | `user.enabled` | Aktif kembali | Pulihkan, hanya bila penangguhan berasal dari X-Guard | | `user.deleted` | Dihapus dari organisasi | Tangguhkan atau hapus akun lokal, cabut sesinya | | `user.apps_changed` | Ditambah ke (`"access": "granted"`) atau dikeluarkan dari (`"revoked"`) daftar akses aplikasi ini | Untuk `revoked`: cabut sesinya | | `webhook.ping` | Uji kirim dari konsol, tanpa `sub` | Jawab 2xx | ## Kiriman ```http POST Content-Type: application/json User-Agent: x-guard-webhook/1 X-XGuard-Event-Id: 6f1c0d2e-… (sama dengan field id) X-XGuard-Event-Type: user.disabled X-XGuard-Timestamp: 1794200000 X-XGuard-Signature: sha256= {"email":"budi@contoh.id","id":"6f1c0d2e-…","occurred_at":"2026-11-09T08:00:00.000000Z","sub":"USR_…","type":"user.disabled"} ``` ## Aturan penerima 1. Hitung HMAC-SHA256 atas **body mentah** (jangan parse lalu serialisasi ulang), dengan secret webhook sebagai kunci dan `timestamp + "." + body` sebagai isi. Bandingkan dengan `X-XGuard-Signature` secara **waktu-konstan**. 2. Tolak timestamp yang selisihnya lebih dari **5 menit** dari jam server (cegah pemutaran ulang). 3. Simpan `id` yang sudah diproses dan abaikan pengulangan, tetapi tetap jawab 2xx. 4. `sub` yang tidak dikenal aplikasi: abaikan, jawab 2xx. 5. Jawab 2xx dalam 10 detik. Selain itu X-Guard mencoba ulang dengan jeda 1 menit, 5 menit, 30 menit, 2 jam, 6 jam, dan 12 jam, sampai 24 jam sejak event terjadi, dengan `id` dan body yang sama. 6. Event untuk pengguna yang sama dikirim **berurutan**: event berikutnya menunggu yang sebelumnya selesai. Riwayat pengiriman (terkirim, menunggu, gagal, dan jawaban terakhir aplikasi) terlihat di panel Webhook. **Uji kirim** mengirim `webhook.ping`. ## Verifikasi **Node.js** ```js import { createHmac, timingSafeEqual } from "node:crypto"; // bodyMentah: Buffer body permintaan apa adanya (mis. express.raw({ type: "application/json" })). export function verifikasiWebhook(secret, headers, bodyMentah) { const ts = headers["x-xguard-timestamp"]; if (!/^\d+$/.test(ts ?? "") || Math.abs(Date.now() / 1000 - Number(ts)) > 300) throw new Error("timestamp"); const harus = Buffer.from("sha256=" + createHmac("sha256", secret).update(`${ts}.`).update(bodyMentah).digest("hex")); const dapat = Buffer.from(headers["x-xguard-signature"] ?? ""); if (harus.length !== dapat.length || !timingSafeEqual(harus, dapat)) throw new Error("tanda tangan"); return JSON.parse(bodyMentah.toString("utf8")); } ``` **PHP** ```php $ts = $request->header('X-XGuard-Timestamp'); $harus = 'sha256='.hash_hmac('sha256', $ts.'.'.$request->getContent(), env('XGUARD_WEBHOOK_SECRET')); abort_unless(ctype_digit((string) $ts) && abs(time() - (int) $ts) <= 300 && hash_equals($harus, (string) $request->header('X-XGuard-Signature')), 401); $event = json_decode($request->getContent(), true); ``` **Python** ```python import hashlib, hmac, json, time def verifikasi_webhook(secret: str, headers: dict, body: bytes) -> dict: ts = headers.get("X-XGuard-Timestamp", "") if not ts.isdigit() or abs(time.time() - int(ts)) > 300: raise ValueError("timestamp") harus = "sha256=" + hmac.new(secret.encode(), ts.encode() + b"." + body, hashlib.sha256).hexdigest() if not hmac.compare_digest(harus, headers.get("X-XGuard-Signature", "")): raise ValueError("tanda tangan") return json.loads(body) ``` **Go:** `xguard.WebhookHandler(secret, fn)` di [SDK Go](/sdk/go). **Laravel:** `XGuardSso::verifyWebhook` di [SDK Laravel](/sdk/laravel). **ASP Classic:** `webhook.asp` di [SDK ASP Classic](/sdk/asp-classic). --- # Sebelum produksi Sumber: https://docs.x-guard.online/panduan/produksi # Sebelum produksi ## Konfigurasi - [ ] Alamat callback dan sesudah logout memakai `https` dan sama persis dengan di konsol. - [ ] Client secret dan secret webhook hanya ada di konfigurasi server, bukan di repositori, kode browser, log, atau chat. - [ ] Jam server tersinkron (selisih di bawah 1 menit). - [ ] Server bisa membuka issuer lewat HTTPS dengan pemeriksaan sertifikat aktif (jangan pernah dimatikan). ## Login - [ ] `state`, `nonce`, dan PKCE S256 dibuat acak per login dan diperiksa saat callback. - [ ] ID token diverifikasi: tanda tangan RS256, `iss`, `aud`, `exp`, `nonce`. - [ ] Akun lokal ditautkan lewat `sub`; tautan lewat email hanya bila `email_verified`. - [ ] Tujuan sesudah login (`next`, `returnTo`) hanya boleh path di aplikasi sendiri, supaya tidak bisa dipakai mengalihkan pengguna ke situs lain. - [ ] Galat callback dipercaya hanya bila `state` cocok; `access_denied` menampilkan pesan, bukan mengulang login. - [ ] Sesi aplikasi dibuat baru sesudah login (ID sesi diganti), supaya ID sesi yang ditanam orang lain tidak berguna. ## Logout dan sesi - [ ] Logout menghapus sesi lokal lalu mengalihkan ke `end_session` dengan `id_token_hint`. - [ ] Back-channel logout dipasang (aplikasi dengan alamat publik), atau login senyap berkala (aplikasi tanpa alamat publik), supaya logout di aplikasi lain ikut terasa. ## Webhook (bila dipakai) - [ ] Tanda tangan dihitung atas body mentah dan dibandingkan waktu-konstan. - [ ] Timestamp lebih dari 5 menit ditolak. - [ ] `id` event yang sudah diproses diabaikan, tetap dijawab 2xx. - [ ] **Uji kirim** di konsol menunjukkan **Terkirim**. ## Uji akhir 1. Login, lalu buka aplikasi organisasi yang lain: masuk tanpa password. 2. Logout di aplikasi lain: aplikasi ini ikut keluar. 3. Admin menonaktifkan akun uji: login ditolak, sesi berakhir (seketika lewat webhook, atau pada pemeriksaan berikutnya). 4. Admin membatasi akses aplikasi ke pengguna lain: akun uji ditolak dengan pesan akses. --- # Ringkasan dan unduhan Sumber: https://docs.x-guard.online/sdk/ # SDK SDK X-Guard adalah kode siap salin, bukan paket yang harus dipasang dari registry. Semuanya memakai OpenID Connect standar, jadi pustaka OIDC lain yang tersertifikasi juga bisa dipakai. | SDK | Untuk | Unduh | Teruji | |---|---|---|---| | [Laravel](/sdk/laravel) | PHP / Laravel 10 sampai 12 | [x-guard-sdk-laravel.zip](/unduh/x-guard-sdk-laravel.zip) | 13 uji PHPUnit dan uji browser terhadap X-Guard sungguhan | | [Go](/sdk/go) | Go 1.24+ (net/http, Gin) | [x-guard-sdk-go.zip](/unduh/x-guard-sdk-go.zip) | Uji paket dengan X-Guard palsu, otomatis di CI | | [ASP Classic](/sdk/asp-classic) | VBScript di IIS, tanpa DLL atau komponen tambahan | [x-guard-sdk-asp-classic.zip](/unduh/x-guard-sdk-asp-classic.zip) | 55 skenario di mesin skrip Windows, termasuk login sungguhan ke tiga organisasi | | [Node.js](/sdk/node) | Node.js 20+ dengan `openid-client` 6 | Contoh di halamannya | | ## Untuk agen AI Setiap berkas SDK bisa diambil mentah tanpa membuka ZIP, mis. `https://docs.x-guard.online/berkas/asp-classic/sso/xg_lib.asp`. Daftar lengkapnya ada di [llms.txt](/llms.txt) dan di bagian **Daftar berkas** tiap halaman SDK. Mulai dari [Brief untuk agen AI](/agen). ## Fitur per SDK | Fitur | Laravel | Go | ASP Classic | |---|---|---|---| | Login dengan PKCE, state, nonce | Ya | Ya | Ya | | Verifikasi tanda tangan ID token | Ya (JWKS) | Ya (JWKS) | Tidak: token diterima langsung dari token endpoint lewat TLS (OpenID Connect Core 3.1.3.7) | | Logout lewat `end_session` | Ya | Ya | Ya | | Back-channel logout | Ya | Ya | Tidak (butuh RSA); diganti pemeriksaan sesi senyap | | Pemeriksaan sesi senyap berkala | Ya | `Silent()` | Ya | | Webhook dengan verifikasi HMAC | Ya | Ya | Ya | | Middleware access token untuk API | | Ya | | --- # Laravel Sumber: https://docs.x-guard.online/sdk/laravel # SSO X-Guard untuk aplikasi Laravel Berkas di folder ini dipasang ke aplikasi Laravel (10, 11, atau 12) supaya pengguna login lewat SSO X-Guard organisasi: OpenID Connect authorization code dengan PKCE, verifikasi ID token dengan kunci publik X-Guard, logout, back-channel logout, pemeriksaan sesi diam-diam, dan webhook. Satu dependensi: `firebase/php-jwt`. Unduh [x-guard-sdk-laravel.zip](/unduh/x-guard-sdk-laravel.zip), atau ambil per berkas di [daftar berkas](#daftar-berkas). Kode yang sama dipakai aplikasi contoh tim X-Guard (dua aplikasi untuk menguji sesi SSO), yang lolos 13 uji PHPUnit dan uji browser terhadap X-Guard sungguhan. ## Berkas | Berkas | Isi | |---|---| | `config/xguard.php` | Issuer, client ID, secret, alamat callback dan sesudah logout, secret webhook | | `app/Services/XGuardSso.php` | Inti OIDC: discovery, URL login + PKCE, penukaran kode, verifikasi token, URL logout, logout token, webhook | | `app/Services/SsoException.php`, `SsoDenied.php` | Galat login | | `app/Http/Controllers/SsoController.php` | Rute `/auth/*`: login, callback, keluar, cek sesi, back-channel logout, webhook | | `app/Http/Middleware/CekSesiXGuard.php` | Menyelaraskan sesi lokal dengan sesi X-Guard | | `database/migrations/..._add_xguard_sub_to_users.php` | Kolom `xguard_sub` di tabel users | ## Memasang 1. Di konsol X-Guard, buka **Aplikasi** → **Tambah aplikasi**, jenis **Web**: - Alamat callback: `https://aplikasi-anda/auth/callback` (`http://localhost:…` boleh untuk pengembangan). - Alamat sesudah logout: `https://aplikasi-anda/`. - Alamat back-channel logout: `https://aplikasi-anda/auth/backchannel-logout`. Simpan client ID dan client secret. Secret hanya tampil sekali. 2. `composer require firebase/php-jwt` 3. Salin berkas di atas ke aplikasi. Daftarkan `XGuardSso` sebagai singleton di `AppServiceProvider::register`: ```php $this->app->singleton(\App\Services\XGuardSso::class, fn () => new \App\Services\XGuardSso(config('xguard'))); ``` 4. Rute dan middleware: ```php // routes/web.php Route::controller(SsoController::class)->prefix('auth')->name('sso.')->group(function () { Route::get('login', 'login')->name('login'); Route::get('callback', 'callback')->name('callback'); Route::get('cek-sesi', 'cekSesi')->middleware('auth')->name('cek-sesi'); Route::post('logout', 'logout')->middleware('auth')->name('logout'); Route::post('backchannel-logout', 'backchannel')->name('backchannel'); Route::post('webhook', 'webhook')->name('webhook'); }); // bootstrap/app.php, di withMiddleware $middleware->redirectGuestsTo('/'); $middleware->alias(['xguard.sesi' => \App\Http\Middleware\CekSesiXGuard::class]); $middleware->validateCsrfTokens(except: ['auth/backchannel-logout', 'auth/webhook']); ``` 5. `.env`: ```dotenv XGUARD_ISSUER=https://nama-organisasi.x-guard.online XGUARD_CLIENT_ID=... XGUARD_CLIENT_SECRET=... XGUARD_REDIRECT_URI=https://aplikasi-anda/auth/callback XGUARD_POST_LOGOUT_URI=https://aplikasi-anda/ XGUARD_WEBHOOK_SECRET= # opsional, dari panel Webhook ``` `CekSesiXGuard` membaca `config('contoh.cek_sesi_detik')` (bawaan 60 detik); ganti dengan konfigurasi aplikasi Anda. 6. `php artisan migrate`, lalu pasang middleware `auth` dan `xguard.sesi` di halaman yang wajib login. ## Perilaku - **Login**: tombol menuju `/auth/login`. Akun lokal dikenali dari `sub` X-Guard; akun lama ditautkan lewat email hanya bila X-Guard menyatakan email itu terverifikasi. - **Logout**: `POST /auth/logout` mengakhiri sesi lokal dan sesi X-Guard (semua aplikasi organisasi ikut keluar). - **Logout dari aplikasi lain**: back-channel logout (aplikasi dengan alamat publik), atau pemeriksaan diam-diam (`prompt=none`) berkala saat halaman dibuka. - **Webhook**: `user.disabled`, `user.deleted`, dan `user.apps_changed` dengan `access` `revoked` mengakhiri semua sesi lokal pengguna itu; `user.updated` memperbarui email. Tanda tangan dan umur kiriman diperiksa, pengulangan event diabaikan berdasarkan id. ## Daftar berkas - [`config/xguard.php`](/berkas/laravel/config/xguard.php) - [`app/Services/XGuardSso.php`](/berkas/laravel/app/Services/XGuardSso.php) - [`app/Services/SsoException.php`](/berkas/laravel/app/Services/SsoException.php) - [`app/Services/SsoDenied.php`](/berkas/laravel/app/Services/SsoDenied.php) - [`app/Http/Controllers/SsoController.php`](/berkas/laravel/app/Http/Controllers/SsoController.php) - [`app/Http/Middleware/CekSesiXGuard.php`](/berkas/laravel/app/Http/Middleware/CekSesiXGuard.php) - [`database/migrations/2026_11_09_000000_add_xguard_sub_to_users.php`](/berkas/laravel/database/migrations/2026_11_09_000000_add_xguard_sub_to_users.php) --- # Go Sumber: https://docs.x-guard.online/sdk/go # SDK Go: paket xguard Paket Go untuk menyambungkan aplikasi ke SSO X-Guard organisasi. Hanya bergantung pada `coreos/go-oidc/v3` dan `golang.org/x/oauth2`. Minimal Go 1.24. | Fungsi | Untuk | |---|---| | `New(ctx, Config)` | Baca discovery issuer organisasi, siapkan client | | `StartLogin(w, r, returnTo, opts...)` | Arahkan browser ke login X-Guard (PKCE S256, state, nonce). `xguard.Silent()` = `prompt=none` | | `HandleCallback(w, r)` | Tukar code, verifikasi ID token → `*Identity` (termasuk `Org` dari access token) | | `LogoutURL(idToken)` | URL logout (RP-initiated); semua aplikasi organisasi ikut keluar | | `BackchannelLogoutHandler(fn)` | Endpoint back-channel logout (X-Guard → aplikasi) | | `WebhookHandler(secret, fn)` | Endpoint webhook `user.*` dan `webhook.ping` (header `X-XGuard-*`) | | `RequireAccessToken(next)` | Middleware API untuk access token JWT dari X-Guard (`Org`, `Scopes`, `Audience`) | ## Memakai Unduh [x-guard-sdk-go.zip](/unduh/x-guard-sdk-go.zip) (atau ambil per berkas di [daftar berkas](#daftar-berkas)), salin folder `xguard` ke `internal/xguard` di aplikasi, lalu `go get github.com/coreos/go-oidc/v3@v3.17.0 golang.org/x/oauth2@v0.35.0 github.com/go-jose/go-jose/v4@v4.1.5`. ```go sso, err := xguard.New(ctx, xguard.Config{ Issuer: os.Getenv("XGUARD_ISSUER"), // https://nama-organisasi.x-guard.online ClientID: os.Getenv("XGUARD_CLIENT_ID"), ClientSecret: os.Getenv("XGUARD_CLIENT_SECRET"), RedirectURL: "https://app.contoh.id/auth/callback", PostLogoutRedirectURL: "https://app.contoh.id/", }) mux.HandleFunc("GET /auth/login", func(w http.ResponseWriter, r *http.Request) { sso.StartLogin(w, r, r.URL.Query().Get("next")) }) mux.HandleFunc("GET /auth/callback", func(w http.ResponseWriter, r *http.Request) { id, next, err := sso.HandleCallback(w, r) if errors.Is(err, xguard.ErrLoginRequired) { sso.StartLogin(w, r, next); return } // sesi X-Guard berakhir → login biasa if errors.Is(err, xguard.ErrAccessDenied) { /* pengguna tidak diberi akses ke aplikasi ini */ } if err != nil { /* *xguard.AuthError lain = state salah / kedaluwarsa */ } // 1. cari user lokal by xguard_sub = id.Subject, fallback email bila id.EmailVerified (lalu isi xguard_sub) // 2. buat sesi aplikasi; simpan id.SessionID (back-channel) dan id.IDToken (logout) http.Redirect(w, r, next, http.StatusFound) }) mux.Handle("POST /auth/backchannel-logout", sso.BackchannelLogoutHandler( func(ctx context.Context, lt *xguard.LogoutToken) error { return sessions.RevokeBySID(ctx, lt.SessionID) })) mux.Handle("POST /auth/webhook", xguard.WebhookHandler(os.Getenv("XGUARD_WEBHOOK_SECRET"), func(ctx context.Context, ev *xguard.Event) error { // Idempoten berdasarkan ev.ID. Sub tidak dikenal → return nil. // user.disabled, user.deleted, atau user.apps_changed dengan ev.Access == xguard.AccessRevoked → cabut sesinya. return nil // error → X-Guard mencoba ulang })) // logout: hapus sesi aplikasi lalu http.Redirect(w, r, sso.LogoutURL(sess.IDToken), http.StatusSeeOther) ``` Dengan **Gin**: `sso.StartLogin(c.Writer, c.Request, next)`, `sso.HandleCallback(c.Writer, c.Request)`, `r.POST(path, gin.WrapH(sso.BackchannelLogoutHandler(fn)))`. ## Tes ```bash go test ./... # memakai X-Guard palsu, tidak butuh server ``` Diangkat dari `pkg/ssoauth` SSO internal; aplikasi lama cukup mengganti import dan issuer. ## Daftar berkas - [`xguard/xguard.go`](/berkas/go/xguard/xguard.go) - [`xguard/login.go`](/berkas/go/xguard/login.go) - [`xguard/backchannel.go`](/berkas/go/xguard/backchannel.go) - [`xguard/webhook.go`](/berkas/go/xguard/webhook.go) - [`xguard/accesstoken.go`](/berkas/go/xguard/accesstoken.go) - [`xguard/xguard_test.go`](/berkas/go/xguard/xguard_test.go) - [`xguard/go.mod`](/berkas/go/xguard/go.mod) - [`xguard/go.sum`](/berkas/go/xguard/go.sum) --- # ASP Classic Sumber: https://docs.x-guard.online/sdk/asp-classic # SSO X-Guard untuk aplikasi ASP Classic Panduan ini untuk developer aplikasi **ASP Classic (VBScript di IIS)** yang ingin login lewat SSO X-Guard organisasi mereka. Semua kode ada di folder `sso/` ([unduh ZIP](/unduh/x-guard-sdk-asp-classic.zip), atau ambil per berkas di [daftar berkas](#daftar-berkas)), siap disalin, dan **tidak butuh DLL, komponen COM tambahan, .NET, atau paket apa pun**. Yang dipakai hanya yang sudah ada di Windows: VBScript, JScript, dan MSXML. **Hasil akhirnya:** - Halaman login aplikasi punya tombol **Masuk dengan X-Guard**. Login password lama boleh tetap ada selama masa transisi. - Pengguna mengisi email dan password di halaman login organisasi di X-Guard, bukan di aplikasi Anda. Aplikasi tidak pernah melihat password. - X-Guard memberi tahu aplikasi siapa penggunanya. **Hak akses (role) tetap diatur di aplikasi Anda**, seperti sekarang. - Opsional: X-Guard mengabari aplikasi lewat webhook saat pengguna dinonaktifkan, dihapus, atau aksesnya dicabut. **Urutan kerja:** | Langkah | Yang dikerjakan | Bagian | |---|---|---| | 1 | Pastikan server memenuhi syarat | [Syarat server](#syarat-server) | | 2 | Daftarkan aplikasi di konsol X-Guard, simpan client ID dan secret | [Daftarkan aplikasi](#daftarkan-aplikasi-di-konsol-x-guard) | | 3 | Salin folder `sso/` ke situs | [Pasang file](#pasang-file) | | 4 | Buka halaman uji koneksi | [Uji koneksi](#uji-koneksi) | | 5 | Sambungkan ke database dan halaman aplikasi | [Sambungkan ke aplikasi](#sambungkan-ke-aplikasi) | | 6 | Opsional: pasang webhook | [Webhook](#webhook-opsional) | | 7 | Uji dari awal sampai akhir | [Daftar uji](#daftar-uji) | Kalau server Anda Windows Server 2012 atau lebih lama, kerjakan Syarat server dan Uji koneksi lebih dulu. Di situlah risiko terbesarnya. --- ## Cara kerjanya X-Guard memakai standar **OpenID Connect** dengan alur *Authorization Code* dan PKCE. Aplikasi menitipkan pengguna ke halaman login organisasi di X-Guard, menerima kembali sebuah kode, lalu menukar kode itu (dari server ke server) dengan data pengguna. ```text Browser pengguna Aplikasi ASP X-Guard | | | | 1. klik Masuk | | |-------------------------->| login.asp | | | buat state, nonce, PKCE, | | | simpan di Session | | 2. dialihkan ke X-Guard | | |<--------------------------| | |------------------------------------------------------>| | | 3. pengguna login | 4. dialihkan kembali: callback.asp?code=...&state=... | |<------------------------------------------------------| |-------------------------->| callback.asp | | | cocokkan state | | | 5. tukar code + secret | | | + code_verifier | | |-------------------------->| | |<--------------------------| | | id_token (data user) | | | 6. periksa id_token, | | | cari user di database, | | | isi Session | | 7. masuk ke aplikasi | | |<--------------------------| | ``` Langkah 5 berjalan dari **server IIS Anda ke X-Guard** lewat HTTPS, bukan lewat browser. Karena itu server IIS harus bisa membuka alamat X-Guard organisasi Anda, dan inilah yang sering jadi kendala di Windows lama. | Istilah | Artinya | |---|---| | Issuer | Alamat X-Guard organisasi Anda, mis. `https://nama-organisasi.x-guard.online`, atau domain sendiri | | `client_id` | Pengenal aplikasi Anda di X-Guard | | `client_secret` | Kata sandi aplikasi Anda ke X-Guard. Hanya boleh ada di server | | `code` | Kode sekali pakai dari X-Guard, dibawa browser ke `callback.asp` | | `state` | Teks acak untuk memastikan callback berasal dari proses login yang dimulai browser ini | | `nonce` | Teks acak yang dikembalikan X-Guard di dalam `id_token`, supaya token lama tidak bisa dipakai ulang | | PKCE | Pasangan `code_verifier` dan `code_challenge`: kode yang bocor tidak bisa ditukar tanpa verifier | | `id_token` | Data pengguna dari X-Guard (nama, email, dll.) | | `sub` | ID pengguna di X-Guard. **Tidak pernah berubah**, walaupun email berubah | --- ## Syarat server ### Aplikasi harus bisa dibuka lewat HTTPS Alamat callback wajib `https://`. Pengecualian hanya `http://localhost` atau `http://127.0.0.1` untuk uji di mesin sendiri. Aplikasi intranet tetap bisa dipakai: X-Guard tidak pernah memanggil aplikasi Anda saat login. Yang perlu bisa membuka alamat aplikasi hanyalah browser pengguna. ### Server IIS harus bisa keluar ke X-Guard Server perlu akses keluar ke alamat X-Guard organisasi Anda di port `443`. Kalau jaringan memakai proxy, atur untuk WinHTTP (lihat [Masalah umum](#masalah-umum)). Pengaturan proxy Internet Explorer tidak berlaku di sini. ### Versi Windows X-Guard (`*.x-guard.online`) hanya menerima **TLS 1.2 atau 1.3**, dengan sertifikat **ECDSA** dari Let's Encrypt (rantai ke ISRG Root X2). Pada TLS 1.2 yang diterima hanya cipher `ECDHE-ECDSA`. Banyak contoh kode ASP lama gagal di titik ini. | Windows Server | Status | Yang perlu dilakukan | |---|---|---| | 2016 atau lebih baru | Cipher tersedia | Tidak ada, langsung ke uji koneksi | | 2012 R2 | Cipher tersedia | Pastikan update KB2919355 terpasang | | 2012 | Perlu patch | Pasang KB3140245, lalu tambah registry di bawah | | 2008 R2 SP1 | Perlu patch | Pasang KB3140245, tambah registry di bawah, dan aktifkan TLS 1.2 | | 2008 (non-R2), 2003 | Tidak tercakup | Lihat [Kalau server tidak bisa diperbarui](#kalau-server-tidak-bisa-diperbarui) | Tabel ini berdasarkan dokumentasi Microsoft. **Hasil akhirnya ditentukan oleh halaman uji di bagian 5**, bukan oleh tabel ini. Organisasi yang memakai domain sendiri bisa mendapat sertifikat yang berbeda; halaman uji juga yang menjawabnya. **Registry untuk Server 2012 dan 2008 R2 SP1** (setelah KB3140245 terpasang). Jalankan di Command Prompt sebagai Administrator: ```bat reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp" /v DefaultSecureProtocols /t REG_DWORD /d 0x800 /f reg add "HKLM\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Internet Settings\WinHttp" /v DefaultSecureProtocols /t REG_DWORD /d 0x800 /f ``` **Tambahan untuk Server 2008 R2 SP1:** ```bat reg add "HKLM\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client" /v DisabledByDefault /t REG_DWORD /d 0 /f ``` Restart server setelah mengubah registry. ### Kalau server tidak bisa diperbarui Yang butuh TLS modern dari server hanya penukaran code (langkah 5). Kalau server tidak bisa diperbarui, jalan keluarnya proxy kecil di mesin lain yang lebih baru, yang meneruskan panggilan itu ke X-Guard. `XG_TOKEN_URL` di `xg_config.asp` sengaja dipisah untuk keperluan ini. ### Pengaturan lain - **Session ASP harus aktif.** Di IIS Manager: ASP > Session Properties > Enable Session State = True. - **Jam server harus akurat.** Selisih dengan X-Guard tidak boleh lebih dari 2 menit, atau login ditolak. - **Satu server, atau sticky session.** Session ASP tersimpan di memori satu server. --- ## Daftarkan aplikasi di konsol X-Guard Admin organisasi masuk ke konsol X-Guard, buka **Aplikasi**, lalu **Tambah aplikasi**: | Isian | Isi | |---|---| | Nama aplikasi | Tampil di halaman login, mis. `Aplikasi Keuangan` | | Jenis | **Web** (aplikasi dengan server) | | Alamat callback | `https://keuangan.contoh.co.id/sso/callback.asp` | | Alamat sesudah logout | `https://keuangan.contoh.co.id/sso/keluar-selesai.asp` | | Alamat back-channel logout | **Kosongkan**: ASP Classic tidak bisa memeriksa tanda tangannya (lihat [Fitur yang sengaja tidak dipakai](#fitur-yang-sengaja-tidak-dipakai)) | | Autentikasi di endpoint token | Bawaan (`client_secret_basic`) | Sesudah disimpan, konsol menampilkan **client ID** dan **client secret**. Secret hanya tampil sekali dan setara dengan password: salin langsung ke `xg_config.asp`, jangan lewat grup chat atau email biasa. Kalau hilang atau bocor, buat secret baru dari rincian aplikasi; secret lama langsung tidak berlaku. **Issuer** organisasi Anda tampil di rincian aplikasi, di panel Kredensial. Bila aplikasi hanya untuk sebagian pengguna, atur di panel **Akses pengguna** pada rincian aplikasi. Pengguna lain ditolak di halaman X-Guard. --- ## Pasang file Unduh [x-guard-sdk-asp-classic.zip](/unduh/x-guard-sdk-asp-classic.zip) dan salin folder `sso/` di dalamnya ke root situs. | File | Isi | Perlu diubah? | |---|---|---| | `xg_config.asp` | Client ID, secret, alamat | **Ya** | | `xg_aplikasi.asp` | Mencari pengguna di database Anda, dan menerima event webhook | **Ya** | | `xg_lib.asp` | Seluruh logika SSO | Tidak | | `login.asp` | Memulai login | Tidak | | `callback.asp` | Menerima pengguna kembali dari X-Guard | Tidak | | `logout.asp` | Keluar dari aplikasi dan X-Guard | Tidak | | `keluar-selesai.asp` | Halaman setelah logout | Boleh dipercantik | | `webhook.asp` | Menerima event dari X-Guard (opsional) | Tidak | | `cek-koneksi.asp` | Halaman uji. **Hapus setelah selesai** | Tidak | Kalau aplikasi berada di virtual directory (mis. `/keuangan/sso/`), sesuaikan `#include virtual` di semua file, serta `XG_LOGIN_PATH` dan alamat di `xg_config.asp`. Di `xg_config.asp`, ganti `nama-organisasi` di empat alamat X-Guard dengan alamat organisasi Anda (atau domain sendiri). Nilai `Const` di VBScript harus teks utuh, jadi keempatnya ditulis lengkap. --- ## Uji koneksi Isi `xg_config.asp`, lalu buka `https://aplikasi-anda/sso/cek-koneksi.asp`. Hasil yang diharapkan: ```text [INFO] Sesi ASP: 123456789 [INFO] Jam server (detik Unix): 1794200000 [OK] Pembuat kode acak (Scriptlet.TypeLib) [OK] SHA-256 (PKCE dan tanda tangan webhook) [OK] HTTPS ke X-Guard dan pembacaan JSON [OK] Alamat login, token, dan logout [OK] Jam server (selisih 1 detik) [OK] Client ID dan secret diterima X-Guard [INFO] Webhook tidak dipakai (XG_WEBHOOK_SECRET kosong) ``` **Jangan lanjut sebelum semua baris `OK`.** Kalau ada yang gagal, lihat [Masalah umum](#masalah-umum). --- ## Sambungkan ke aplikasi ### Tambah kolom di tabel user ```sql ALTER TABLE users ADD xguard_sub VARCHAR(64) NULL; CREATE UNIQUE INDEX ux_users_xguard_sub ON users (xguard_sub) WHERE xguard_sub IS NOT NULL; ``` ### Sesuaikan `xg_aplikasi.asp` `xgLoginLokal(xgSub, email, nama, emailTerverifikasi)` dipanggil sesudah X-Guard memastikan identitas pengguna. Contohnya: 1. Mencari pengguna lewat `xguard_sub`. 2. Bila belum ketemu (login pertama), mencari lewat email lalu mengisi `xguard_sub`, **hanya bila email terverifikasi**. 3. Bila tetap tidak ketemu, menolak dengan pesan. 4. Mengisi `Session`. Ganti `APP_DB_CONN`, nama tabel dan kolom, serta `Session("user_id")` dan `Session("user_nama")` dengan nama Session yang **sama persis** dengan yang diisi halaman login lama. Dengan begitu seluruh halaman aplikasi langsung mengenali pengguna. ### Tombol login dan logout ```html Masuk dengan X-Guard Keluar ``` `next` adalah tujuan setelah login, dan hanya boleh berupa path di aplikasi ini. ### Pemeriksaan login di tiap halaman Selama masa transisi, tidak ada yang perlu diubah: `xgLoginLokal` mengisi Session yang sama dengan login lama. Setelah semua pengguna lewat X-Guard, ganti isi file pemeriksaan login yang sudah ada (biasanya satu include seperti `cek_login.asp`) dengan: ```vb <% xgWajibLogin False %> ``` Untuk halaman yang dipanggil lewat AJAX, pakai `xgWajibLogin True`, supaya halaman itu menjawab `401` dan tidak mengalihkan permintaan AJAX ke X-Guard. Yang didapat: - Pengguna yang belum login dialihkan ke X-Guard, lalu kembali ke halaman yang tadi dibuka. - Bila sesi ASP habis tapi sesi X-Guard masih ada, pengguna masuk lagi otomatis tanpa mengklik apa pun. - Tiap 15 menit aplikasi memeriksa ulang ke X-Guard tanpa terlihat. Pengguna yang sudah keluar di aplikasi lain, akunnya dinonaktifkan, atau aksesnya dicabut ikut keluar dari aplikasi ini. --- ## Webhook (opsional) Webhook membuat aplikasi tahu seketika saat pengguna dinonaktifkan atau dihapus, tanpa menunggu pengguna itu membuka halaman. Cocok untuk menandai akun lokal nonaktif. 1. Di konsol X-Guard, buka rincian aplikasi, panel **Webhook**, lalu **Pasang webhook**: alamat `https://aplikasi-anda/sso/webhook.asp`, pilih event yang dibutuhkan. 2. Salin **secret webhook** (diawali `xgwh_`, hanya tampil sekali) ke `XG_WEBHOOK_SECRET` di `xg_config.asp`. 3. Sesuaikan `xgTerimaEvent` di `xg_aplikasi.asp`. 4. Tekan **Uji kirim** di panel Webhook. Riwayat pengiriman di panel itu harus menunjukkan **Terkirim**. `webhook.asp` memeriksa tanda tangan `X-XGuard-Signature` (HMAC-SHA256 atas `timestamp.body`), menolak kiriman yang lebih tua dari 5 menit, lalu memanggil `xgTerimaEvent(idEvent, tipe, sub, email, akses)`: | Event | Artinya | |---|---| | `user.created` | Akun aktif pertama kali | | `user.updated` | Nama atau email berubah | | `user.disabled` | Ditangguhkan atau dinonaktifkan admin organisasi | | `user.enabled` | Aktif kembali | | `user.deleted` | Dihapus | | `user.apps_changed` | Pengguna ditambah ke (`akses = "granted"`) atau dikeluarkan dari (`"revoked"`) daftar akses aplikasi ini | `xgTerimaEvent` mengembalikan `""` bila event diterima. Pesan lain membuat X-Guard mencoba lagi dengan jeda bertahap sampai 24 jam, jadi event bisa datang lebih dari sekali: simpan `idEvent` yang sudah diproses dan abaikan pengulangan. `sub` yang tidak dikenal aplikasi cukup diabaikan (kembalikan `""`). Sesi ASP pengguna yang dinonaktifkan tidak bisa diakhiri dari webhook (sesi ada di memori server, per browser). Sesi itu berakhir saat cek ulang berikutnya (paling lambat 15 menit), atau lebih cepat bila halaman memeriksa status akun lokal. --- ## Daftar uji | No | Lakukan | Yang seharusnya terjadi | |---|---|---| | 1 | Klik **Masuk dengan X-Guard**, login | Kembali ke aplikasi sebagai pengguna itu | | 2 | Cek kolom `xguard_sub` di database | Terisi (diawali `USR_`) | | 3 | Logout, lalu login lagi | Masuk sebagai pengguna yang sama | | 4 | Login dengan akun yang tidak ada di tabel `users` | Pesan "belum terdaftar" | | 5 | Klik **Keluar** | Sampai di `keluar-selesai.asp` | | 6 | Setelah keluar, buka halaman dalam | Diminta login lagi | | 7 | Buka `/sso/login.asp?next=https://google.com`, login | Mendarat di halaman awal aplikasi, **bukan** Google | | 8 | Belum login, buka `/sso/callback.asp?code=abc&state=xyz` | Ditolak: "Sesi login sudah kedaluwarsa" | | 9 | Admin menonaktifkan akun uji di konsol | Paling lambat 15 menit kemudian terlempar keluar | | 10 | Webhook: **Uji kirim** di konsol | Riwayat pengiriman **Terkirim** | **Setelah semua lolos, hapus `cek-koneksi.asp`.** --- ## Data pengguna dari X-Guard | Klaim `id_token` | Isi | Parameter di `xgLoginLokal` | |---|---|---| | `sub` | ID pengguna (diawali `USR_`). Pakai ini sebagai kunci | `xgSub` | | `email` | Email utama | `email` | | `email_verified` | Email sudah diverifikasi | `emailTerverifikasi` | | `name` | Nama lengkap | `nama` | | `sid` | Sesi X-Guard | Disimpan di `Session("xg_sid")` | | `amr` | Cara login, mis. `pwd` | Tidak dipakai di contoh | X-Guard **tidak** mengirim role atau jabatan. Hak akses di aplikasi tetap urusan aplikasi. --- ## Perilaku yang perlu diketahui - **Lama sesi.** Sesi ASP mengikuti `Session.Timeout` (bawaan IIS 20 menit tanpa aktivitas), dan pulih otomatis selama sesi X-Guard masih ada. - **Logout berlaku untuk semua aplikasi organisasi.** `logout.asp` juga mengakhiri sesi X-Guard. - **Logout dari aplikasi lain tidak langsung terasa di sini.** Aplikasi ASP baru tahu saat cek ulang berikutnya, paling lambat 15 menit. Atur lewat `XG_CEK_ULANG_MENIT`. Cek ulang hanya pada permintaan GET non-AJAX, supaya isian form tidak hilang. - **Akun yang dinonaktifkan** tidak bisa login lagi seketika, dan sesi X-Guard-nya diakhiri. Sesi di aplikasi ini berakhir saat cek ulang. - **Akses yang dicabut** di konsol: pengguna ditolak di cek ulang berikutnya dengan pesan "belum diberi akses". --- ## Masalah umum Kegagalan login tercatat di log IIS, di kolom `cs-uri-query`, dengan awalan `xg_gagal_`; webhook dengan awalan `xg_webhook_`. ### Di `cek-koneksi.asp` | Pesan | Penyebab | Yang dilakukan | |---|---|---| | `0x80072F7D` atau `0x80072F8F` | Windows tidak bisa membangun TLS dengan X-Guard | Lihat [Versi Windows](#versi-windows) | | `0x80072F0D` | Windows tidak mempercayai penerbit sertifikat | Jalankan Windows Update, atau pasang **ISRG Root X2** dari `letsencrypt.org/certificates` ke *Local Computer > Trusted Root Certification Authorities*. Jangan matikan pemeriksaan sertifikat | | `0x80072EE7` | Server tidak bisa menerjemahkan nama X-Guard | Periksa DNS server | | `0x80072EFD` atau `0x80072EE2` | Koneksi diblokir atau habis waktu | Periksa firewall. Lewat proxy: `netsh winhttp set proxy proxy.kantor:8080` | | `HTTP 404 (periksa XG_ISSUER)` | Alamat organisasi salah | Salin issuer dari rincian aplikasi di konsol | | `Issuer X-Guard ... tidak sama` | Ada garis miring di akhir, atau huruf berbeda | Tulis persis seperti di konsol | | `XG_AUTH_URL ... tidak sama` | Tiga alamat lain belum ikut diganti | Ganti `nama-organisasi` di keempat alamat | | `Jam server selisih ...` | Jam server tidak akurat | Perbaiki sinkronisasi waktu | | `Client ID atau secret ditolak` | Salah salin, atau secret sudah diganti | Periksa `xg_config.asp`, atau buat secret baru di konsol | ### Saat login | Gejala | Penyebab | Yang dilakukan | |---|---|---| | Halaman X-Guard menampilkan galat soal alamat callback | `XG_REDIRECT_URI` tidak sama persis dengan yang didaftarkan | Bandingkan huruf per huruf | | "Sesi login sudah kedaluwarsa" (`xg_gagal_state`) | Login dimulai dari alamat berbeda dengan alamat callback (mis. tanpa `www` lalu kembali ke `www`) | Pakai satu alamat yang sama | | `xg_gagal_token_kedaluwarsa` atau `..._dari_masa_depan` | Jam server tidak akurat | Perbaiki sinkronisasi waktu | | `xg_gagal_koneksi` | Halaman uji lolos tapi login gagal | Buka lagi `cek-koneksi.asp` | | `xg_gagal_token_invalid_grant` | Code sudah terpakai atau kedaluwarsa | Coba login lagi | | "Belum diberi akses ke aplikasi ini" | Akses pengguna dibatasi di konsol | Minta admin organisasi menambahkannya di panel Akses pengguna | | "Belum terdaftar di aplikasi ini" | Email pengguna tidak ada di tabel `users` | Tambahkan pengguna, atau ubah `xgLoginLokal` | ### Webhook | Riwayat pengiriman di konsol | Penyebab | |---|---| | `HTTP 401: tanda_tangan_salah` | `XG_WEBHOOK_SECRET` salah, atau secret webhook sudah diganti | | `HTTP 401: timestamp_kedaluwarsa` | Jam server tidak akurat | | `HTTP 401: secret_kosong` | `XG_WEBHOOK_SECRET` belum diisi | | `HTTP 500: ...` | `xgTerimaEvent` mengembalikan pesan galat; X-Guard mencoba lagi | | `HTTP 404` | Alamat webhook salah, atau `webhook.asp` belum disalin | ### Galat ASP | Pesan | Penyebab | Yang dilakukan | |---|---|---| | `Name redefined` | File ter-include dua kali di satu halaman | Sisakan satu | | `Type mismatch: 'xgUrlEncode'` | `xg_lib.asp` tidak ter-include | Periksa `#include` | | `Expected literal constant` | Nilai `Const` memakai `&` | Tulis teks utuh | --- ## Keamanan 1. **Jangan pernah mematikan pemeriksaan sertifikat** (`http.setOption 2, 13056`). Aplikasi tidak memeriksa tanda tangan `id_token` dan mengandalkan sertifikat X-Guard. Tanpa pemeriksaan itu, siapa pun di jaringan bisa memalsukan pengguna. 2. **Simpan secret hanya di file `.asp`**, bukan `.inc`, `.txt`, atau `.config` di folder web. 3. **Jangan pakai `Rnd()`** untuk `state`, `nonce`, atau verifier. Pakai `xgAcak()`. 4. **Jangan menghapus pemeriksaan `state`, `nonce`, atau tanda tangan webhook.** 5. **Jangan menerima `id_token` dari form atau URL.** `xgPeriksaIdToken` hanya aman untuk token yang diterima langsung dari X-Guard di `callback.asp`. 6. **Jangan menggabung teks ke dalam SQL.** Pakai parameter seperti contoh di `xg_aplikasi.asp`. 7. **Jangan mencatat `client_secret`, secret webhook, `code`, atau `id_token`** ke log atau ke layar. 8. **Hapus `cek-koneksi.asp`** setelah selesai. **Kenapa tanda tangan `id_token` tidak diperiksa?** ASP Classic tidak punya fungsi kriptografi RSA. OpenID Connect Core 3.1.3.7 butir 6 mengizinkan hal ini bila token diterima langsung dari token endpoint lewat TLS, karena sertifikat HTTPS sudah membuktikan token itu dari X-Guard. Isi token (`iss`, `aud`, `exp`, `nonce`) tetap diperiksa. **Kenapa ada cookie `XGAUTH`?** ASP Classic tidak bisa mengganti `ASPSESSIONID` setelah login. Cookie acak ini mengikat sesi ke browser yang benar-benar menyelesaikan login. --- ## Status pengujian Kode di folder ini dijalankan di mesin VBScript, JScript, dan MSXML Windows 11 (`cscript`), dengan objek `Session`, `Request`, `Response`, dan `Server` tiruan: - 40 skenario tanpa jaringan: SHA-256, HMAC-SHA256, dan PKCE terhadap vektor resmi dan Node; pemeriksaan `id_token` (iss, aud, exp, nonce, sub); state palsu; `access_denied`; login senyap gagal; pengalihan ke situs lain; webhook sah, body diubah, secret lain, timestamp lama, dan teks Unicode. - 15 skenario terhadap X-Guard sungguhan: `cek-koneksi.asp`, login dengan PKCE dan client secret, callback dobel, cookie pengikat, cek ulang senyap, logout, dan `login_required` sesudah logout. **Belum diuji:** berjalan di IIS sungguhan. Kerjakan [daftar uji](#daftar-uji) di server Anda. `xg_aplikasi.asp` baru lolos kompilasi, karena tiap aplikasi berbeda. --- ## Fitur yang sengaja tidak dipakai | Fitur | Kenapa | Gantinya | |---|---|---| | Back-channel logout | Logout token harus diperiksa tanda tangan RSA-nya | Cek ulang berkala (`XG_CEK_ULANG_MENIT`) | | Refresh token | Aplikasi hanya perlu tahu siapa penggunanya | Login senyap saat sesi habis | --- ## Daftar berkas Semua berkas juga bisa diambil satu per satu (teks, baris CRLF): - [`sso/xg_config.asp`](/berkas/asp-classic/sso/xg_config.asp) - [`sso/xg_aplikasi.asp`](/berkas/asp-classic/sso/xg_aplikasi.asp) - [`sso/xg_lib.asp`](/berkas/asp-classic/sso/xg_lib.asp) - [`sso/login.asp`](/berkas/asp-classic/sso/login.asp) - [`sso/callback.asp`](/berkas/asp-classic/sso/callback.asp) - [`sso/logout.asp`](/berkas/asp-classic/sso/logout.asp) - [`sso/keluar-selesai.asp`](/berkas/asp-classic/sso/keluar-selesai.asp) - [`sso/webhook.asp`](/berkas/asp-classic/sso/webhook.asp) - [`sso/cek-koneksi.asp`](/berkas/asp-classic/sso/cek-koneksi.asp) --- # Node.js Sumber: https://docs.x-guard.online/sdk/node # Node.js X-Guard tidak butuh SDK khusus untuk Node.js: pakai [`openid-client`](https://github.com/panva/openid-client) versi 6, pustaka OpenID Connect yang tersertifikasi. Contoh di bawah memakai Express dan `express-session`. ```bash npm install openid-client express express-session ``` ```js import express from "express"; import session from "express-session"; import * as client from "openid-client"; const ISSUER = process.env.XGUARD_ISSUER; // https://nama-organisasi.x-guard.online const REDIRECT = "https://app.contoh.id/auth/callback"; const SETELAH_LOGOUT = "https://app.contoh.id/"; // Membaca discovery issuer. Aplikasi Web: client secret dari konfigurasi server. const config = await client.discovery(new URL(ISSUER), process.env.XGUARD_CLIENT_ID, process.env.XGUARD_CLIENT_SECRET); const app = express(); app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { httpOnly: true, secure: true, sameSite: "lax" } })); app.get("/auth/login", async (req, res) => { const verifier = client.randomPKCECodeVerifier(); const state = client.randomState(); const nonce = client.randomNonce(); req.session.xguard = { verifier, state, nonce }; res.redirect(client.buildAuthorizationUrl(config, { redirect_uri: REDIRECT, scope: "openid profile email", state, nonce, code_challenge: await client.calculatePKCECodeChallenge(verifier), code_challenge_method: "S256", }).href); }); app.get("/auth/callback", async (req, res) => { const alur = req.session.xguard; delete req.session.xguard; if (!alur) return res.status(400).send("Sesi login kedaluwarsa, ulangi login."); try { // Memeriksa state, menukar code dengan PKCE, dan memverifikasi ID token (tanda tangan, iss, aud, exp, nonce). const tokens = await client.authorizationCodeGrant(config, new URL(req.originalUrl, REDIRECT), { pkceCodeVerifier: alur.verifier, expectedState: alur.state, expectedNonce: alur.nonce, }); const { sub, email, email_verified, name, sid } = tokens.claims(); // Tautkan akun lokal lewat sub (email hanya bila email_verified), lalu buat sesi baru. req.session.regenerate(() => { req.session.pengguna = { sub, email, name, sid, idToken: tokens.id_token }; res.redirect("/"); }); } catch (e) { // e.error === "access_denied": pengguna tidak diberi akses ke aplikasi ini. res.status(401).send(`Login gagal: ${e.error ?? e.message}`); } }); app.post("/auth/logout", (req, res) => { const idToken = req.session.pengguna?.idToken; req.session.destroy(() => { res.redirect(client.buildEndSessionUrl(config, { id_token_hint: idToken, post_logout_redirect_uri: SETELAH_LOGOUT, }).href); }); }); ``` ## SPA dan aplikasi tanpa secret Aplikasi berjenis SPA atau native tidak punya client secret; PKCE wajib: ```js const config = await client.discovery(new URL(ISSUER), process.env.XGUARD_CLIENT_ID, undefined, client.None()); ``` ## Webhook Pakai body mentah (`express.raw`), bukan JSON yang sudah di-parse. Fungsi `verifikasiWebhook` ada di [Webhook](/panduan/webhook#verifikasi). ```js app.post("/auth/webhook", express.raw({ type: "application/json" }), (req, res) => { let ev; try { ev = verifikasiWebhook(process.env.XGUARD_WEBHOOK_SECRET, req.headers, req.body); } catch { return res.status(401).end(); } // Idempoten berdasarkan ev.id. user.disabled / user.deleted / user.apps_changed (access "revoked"): cabut sesinya. res.status(204).end(); }); ``` ## Back-channel logout `openid-client` tidak menyediakan pemeriksaan logout token. Pakai pustaka JWT seperti `jose`: verifikasi dengan `createRemoteJWKSet(new URL(ISSUER + "/keys"))`, periksa `iss`, `aud`, klaim `events`, dan tidak adanya `nonce`, lalu cabut sesi yang `sid`-nya sama. Rincian di [Logout dan sesi](/panduan/logout#back-channel-logout).