sauth — Gambaran Umum & Latar Belakang
sauth adalah kumpulan tiga library — satu Composer package PHP untuk penerbitan token, satu untuk validasi, dan satu npm package TypeScript untuk browser — yang bersama-sama membentuk satu lapisan autentikasi terpusat di seluruh gateway dan resource server.
TL;DR
| BpmJWT | sauth | |
|---|---|---|
| Kepemilikan key | Private/public key yang sama disalin ke setiap repo | Setiap gateway punya key pair sendiri; resource server hanya menyimpan public key |
| Siapa yang bisa menerbitkan token | Service mana pun yang berjalan secara lokal | Hanya gateway (GWA, GWC) — resource server tidak pernah menerbitkan token |
| Dev mocking | Salin key + debug endpoint isLocal(), permission default diam-diam diset ke 'wm' | Env var SAUTH_BYPASS + fake claims JSON file, tidak perlu key |
| M2M token | Tidak ada konsep — bentuknya sama dengan user token | Pemisahan struktural: tidak ada snm = M2M; middleware sauth.m2m yang khusus |
| Akses pihak ketiga (3p) | Tidak mungkin — tidak ada claim scope | OAuth PKCE + claim scope lengkap; developer eksternal bisa mengakses resource server dengan aman |
| Long-lived token | Token dengan exp tinggi secara ad-hoc, tanpa mekanisme pencabutan | API key JWT dengan jti UUID + tabel blacklist per-service |
| Middleware | JwtVerification disalin-tempel ke setiap service, logikanya menyimpang seiring waktu | Satu library sauth-client, lima middleware bernama dengan tanggung jawab yang jelas |
| Validasi | Pengecekan string pada iss + verifikasi dengan shared key | Public key per-issuer + signature kriptografis + pengecekan claim aud |
Singkatnya: BpmJWT adalah JWT helper berbasis shared key di mana service mana pun yang memiliki key bisa memalsukan token untuk service lain. sauth memberikan key pair tersendiri untuk setiap gateway, menstandarisasi bentuk token di seluruh service, menambahkan OAuth flow yang proper untuk M2M dan akses pihak ketiga, serta menggantikan long-lived token ad-hoc dengan API key JWT yang bisa diaudit.
Masalah Sebelum sauth
Sebelum sauth, setiap service menyimpan salinannya sendiri dari sebuah helper class JWT bernama BpmJWT, beserta pasangan private key dan public key secara lengkap.
Cara Kerja BpmJWT
Sebuah resource server seperti PNR menyimpan BpmJWT.php, private key, dan public key di direktori storage/-nya sendiri. Saat pengembangan lokal, ada debug endpoint (hanya terdaftar ketika app()->isLocal()) yang memungkinkan developer menerbitkan token langsung dari resource server itu sendiri:
// routes/api.php — hanya di lokal
if (app()->isLocal()) {
Route::prefix('/token')->controller(JwtController::class)->group(function () {
Route::post('/issue', 'apiIssue'); // terbitkan token
Route::post('/claim', 'apiClaims'); // baca claims
Route::post('/refresh', 'apiRefresh');
Route::post('/destroy', 'revokeToken');
});
}issueToken() membangun payload dan menandatanganinya dengan private key resource server itu sendiri:
$accessPayload = [
"iss" => "03-pnr", // PNR menerbitkan tokennya sendiri
"aud" => $target,
"iat" => now()->timestamp,
"exp" => now()->addMinutes(10)->timestamp,
"sid" => $userId,
"snm" => $userName,
"fet" => $permissions ?? 'wm', // default: webmaster bypass
];Akar Masalahnya
Shared Key Pair
GWA dan GWC menggunakan pasangan private/public key yang sama, dan setiap resource server menyimpan salinan keduanya. Sebuah JWT yang ditandatangani oleh PNR secara kriptografis identik dengan yang ditandatangani GWA — key-nya sama, signature-nya sama. Satu-satunya perlindungan hanyalah pengecekan string pada claim iss.
Local Mode Memperluas Daftar Issuer yang Dipercaya
// JwtVerification.php
if (!in_array($decoded->iss, app()->isLocal() ? $code : ['01-gwa', '02-gwc'])) {
throw new \Exception('Invalid issuer', 403);
}Di local mode, $code mencakup semua kode service termasuk '03-pnr'. Artinya, service apa pun yang berjalan secara lokal bisa menerbitkan token yang lolos validasi di service mana pun.
Default Webmaster yang Diam-diam
public function issueToken(string $userId, string $target = '', string|array $permissions = null): array
{
// ...
$accessToken = $this->theAccessToken(
$userName,
$userId,
$permissions ?? 'wm', // lupa isi permissions? dapat akses webmaster
$targetStr
);Lupa mengisi $permissions secara diam-diam memberikan akses level webmaster ke mana-mana.
Tidak Ada Konsep M2M, Scope, maupun Dukungan 3p
BpmJWT tidak mengenal konsep machine-to-machine token, tidak ada OAuth scope claim, dan tidak ada cara yang aman untuk menerbitkan token ke developer eksternal. Setiap token terlihat sama, tidak peduli apakah yang memakainya adalah pengguna manusia atau background job.
Long-Lived Token Tidak Bisa Dicabut
Untuk integrasi yang stabil, developer menerbitkan access token berumur panjang (nilai exp tinggi) atau menjaga token tetap hidup via refresh. Satu-satunya "pencabutan" adalah Cache::forget() pada refresh cache key — yang hanya memblokir pembaruan token, tidak membatalkan access token yang sudah ada dan masih dalam window exp-nya. Tidak ada identifier jti, tidak ada blacklist.
Middleware yang Disalin-Tempel dengan Logika yang Berbeda-beda
JwtVerification diduplikasi ke setiap resource server dengan modifikasi lokal masing-masing — daftar issuer berbeda, pesan error berbeda, logika permission yang halus berbeda. Tidak ada satu sumber kebenaran.
Apa yang Digantikan sauth
| Konsep BpmJWT | Pengganti di sauth |
|---|---|
| Shared key pair di setiap repo | Setiap gateway punya key pair sendiri; resource server hanya menyimpan public key |
Debug endpoint isLocal() untuk menerbitkan token | SAUTH_BYPASS + fake claims JSON file — tanpa key, tanpa endpoint |
Default permission 'wm' yang diam-diam | Tidak ada default — sid kosong di fake claims → 401 |
| Tidak ada pembedaan M2M | Struktural: snm tidak ada = M2M; middleware sauth.m2m |
| Tidak ada OAuth scope | Dukungan penuh claim scope; membuka akses developer eksternal (3p) |
| Long-lived token tanpa mekanisme pencabutan | API key JWT dengan jti UUID + tabel blacklist per-service |
JwtVerification yang diduplikasi per service | Satu library sauth-client yang dipasang di mana-mana |
| Long-lived token tanpa konsep formal | bpm:sauth:apikey issue — API key JWT yang proper dengan audit trail |
Arsitektur
┌──────────────────────────────────────────────────────────────┐
│ GWA (sa-gw-admin) GWC (sa-gw-customer-2) │
│ │
│ sauth-server ← menerbitkan JWT sauth-server ← menerbitkan│
│ sauth-client ← memvalidasi sauth-client ← memvalidasi│
│ sauth-frontend (npm) sauth-frontend (npm) │
│ │
│ OAuth IdP pusat — satu-satunya Gateway pelanggan saja │
│ otoritas M2M token dan Tidak ada M2M, tidak ada │
│ token exchange (RFC 8693) token exchange │
└────────────────────┬────────────────────┬────────────────────┘
│ Bearer JWT │ Bearer JWT
▼ ▼
┌──────────────────────────────────────┐
│ Resource Servers (SRF, PNR, PEL ...) │
│ sauth-client saja │
│ Tanpa Passport; tanpa DB untuk token │
│ standar (DB hanya untuk JTI blacklist│
│ Validasi via firebase/php-jwt │
└──────────────────────────────────────┘GWA adalah satu-satunya otoritas M2M. Semua token client_credentials dan permintaan token exchange diarahkan ke GWA. GWC hanya menerbitkan user token.
Resource server tidak pernah menghubungi gateway saat validasi. Mereka menyimpan public key gateway di config dan memverifikasi signature JWT secara lokal — tanpa network hop. DB hanya diakses ketika token membawa claim jti (API key JWT), dan hanya jika service tersebut sudah menjalankan migrasi JTI blacklist.
Tiga Library
| Library | Package | Peran |
|---|---|---|
sauth-server | bpmlib/sauth-server | Penerbitan token — membungkus Passport, menyuntikkan ecosystem claims |
sauth-client | bpmlib/sauth-client | Validasi token — stateless JWT guard dan middleware |
sauth-frontend | @bpmlib/sauth-frontend | Utilitas permission frontend + authenticated HTTP client |
Siapa Memasang Apa
| Aplikasi | sauth-server | sauth-client | sauth-frontend |
|---|---|---|---|
| GWA | ✓ | ✓ (via dependensi sauth-server) | ✓ |
| GWC | ✓ | ✓ (via dependensi sauth-server) | ✓ |
| Resource server (SRF, PNR, …) | ✗ | ✓ | ✗ |
sauth-frontend menggantikan utils-sarequest
Jangan pasang @bpmlib/sauth-frontend dan @bpmlib/utils-sarequest sekaligus dalam satu aplikasi.
Kontrak Token Claim
Nama-nama claim ini adalah kontrak bersama di ketiga library. Mengganti nama claim mana pun mengharuskan pembaruan ketiganya dalam satu rilis yang sama.
| Claim | Tipe | Ada di |
|---|---|---|
iss | string | Semua token — kode singkat (gwa/gwc) untuk token 1p; APP_URL untuk token 3p dan API key JWT |
aud | string | Semua token — kode service tujuan (srf, pnr, pel, dll.) |
iat | int | Semua token |
exp | int | Semua token kecuali API key JWT |
sid | string | Semua token — user ID / client ID / nama aplikasi |
snm | string | Hanya user token — tidak ada di M2M token |
fet | string|array | Hanya user token — "wm" = webmaster bypass; array permission atau kode role. Tidak pernah ada di M2M token |
scope | string | Token 3p user + 3p M2M + API key JWT — dipisah spasi, diawali kode service |
jti | string | Hanya API key JWT — UUID yang dipakai sebagai kunci blacklist |
act | object|null | Hanya token exchange — {sub: calling_client_id} |
fp | string | Semua token (v0.3+) — '1p' atau '3p' |
Membaca Jenis Token
fp | snm | scope | jti | Jenis token | Gunakan middleware |
|---|---|---|---|---|---|
'1p' | ada | tidak ada | tidak ada | 1p user token | sauth.gate |
'3p' | ada | ada | tidak ada | 3p user token | sauth.gate + sauth.scope |
'1p' | tidak ada | tidak ada | tidak ada | 1p M2M | sauth.gate (tanpa params) atau sauth.m2m |
'3p' | tidak ada | ada | tidak ada | 3p M2M (OAuth) | sauth.scope |
'3p' | tidak ada | ada | ada | API key JWT | sauth.scope |
| apa pun | ada | apa pun | tidak ada | Delegated (token exchange) | sauth.gate |
Untuk token yang diterbitkan sebelum v0.3 (tanpa fp), gunakan inferensi dari snm/scope/jti.
Token Flows
Metafora Ticketbooth
Nama ticketbooth terinspirasi dari cara kerja taman hiburan — dan analogi ini sangat pas untuk menjelaskan dua flow utama yang melibatkan pengguna.
Gateway adalah pintu masuk taman hiburan. Anda melakukan autentikasi sekali di sana (halaman login). Bukti masuk adalah gelang tanganmu — dalam istilah sauth, HttpOnly session cookie yang dikelola oleh server.
Ticketbooth (
POST /api/sauth/token) adalah loket di dalam taman. Ketika ingin menaiki wahana tertentu (memanggil SRF, PNR, dll.), anda berjalan ke loket, menunjukkan gelang tangan, dan bilang "satu tiket untuk SRF." Loket mencetak tiket wahana berumur pendek (JWT denganaud=srf). Tiket itu anda tunjukkan di pintu masuk wahana — resource server — yang memeriksanya secara lokal tanpa menghubungi pintu masuk utama.Ketika tiket kedaluwarsa, anda kembali ke ticketbooth — bukan ke pintu masuk. Gelang tanganmu (session) masih berlaku. Anda mendapat tiket baru dalam satu perjalanan singkat. Tidak perlu login ulang.
Inilah session mode. Gelang tangan adalah credential jangka panjang; tiket wahana adalah JWT berumur pendek.
PKCE mode berbeda — bayangkan pemegang annual pass.
Pemegang annual pass tidak perlu antre di pintu masuk setiap hari. Mereka punya kartu keanggotaan (access token) yang langsung mengizinkan masuk. Kartu itu punya tanggal kedaluwarsa.
Ketika kartu kedaluwarsa, pemegang menggunakan kupon perpanjangan (refresh token) di loket keanggotaan (
POST /oauth/token { grant_type: refresh_token }) untuk mendapat kartu baru — tanpa harus kembali ke pintu masuk atau login ulang.Jika kartu dan kuponnya hilang keduanya, pemegang harus mendaftar ulang dari awal di pintu masuk (ulangi PKCE). Inilah yang ditangani oleh
onAuthFailure().
Inilah PKCE mode. Refresh token ada di sini justru karena tidak ada gelang tangan — tidak ada server session untuk diandalkan. Session mode sengaja tidak memakai refresh token karena gelang tangan sudah mengisi peran itu, dan refresh token yang bisa dibaca JavaScript akan menambah permukaan serangan tanpa manfaat apa pun.
M2M adalah kartu staf. Pekerja tidak perlu antre di ticketbooth sama sekali. Mereka punya credential staf permanen sendiri (client_id + client_secret) dan menggunakan pintu belakang (/oauth/token { grant_type: client_credentials }). Kartu mereka tidak ada namanya (snm tidak ada), tidak ada hak khusus personal (fet tidak ada) — kepercayaan bersifat struktural. Jika kartu yang di-cache mendapat 401, mereka otomatis mengambil yang baru.
API key JWT adalah vendor season pass. Operator stan makanan (partner eksternal) mendapat lencana laminasi yang diterbitkan langsung oleh manajemen taman (bpm:sauth:apikey issue). Tidak ada tanggal kedaluwarsa — berlaku selamanya. Tapi ada nomor seri (jti). Jika lencana hilang atau dicuri, manajemen memblokir nomor seri itu di setiap wahana yang terpengaruh (bpm:sauth:jti block {jti}) tanpa menarik lencana orang lain.
Session-backed frontend (GWA / GWC Inertia)
Pengguna autentikasi via Laravel Breeze session. Ketika frontend perlu memanggil resource server:
1. Pengguna login → HttpOnly session cookie diset oleh gateway
2. Frontend perlu memanggil SRF:
POST /api/sauth/token { target: 'srf' } ← auth:sanctum (GWA: baris 130 api.php)
← { access: <jwt>, expires_in: 900 }
3. sauth-frontend menyimpan JWT di cookie tkac_srf (TTL = 80% dari expires_in)
4. Frontend memanggil SRF langsung:
GET https://srf.internal/api/data
Authorization: Bearer <jwt>
5. SRF memvalidasi: signature + exp + aud — tanpa DB, tanpa panggilan PassportTidak ada refresh token di session mode
Gelang tangan (HttpOnly session cookie) adalah credential jangka panjang. Menerbitkan ulang dari ticketbooth itu murah dan aman dari XSS. Refresh token yang bisa dibaca JavaScript hanya menambah permukaan serangan tanpa manfaat — session sudah menjalankan peran itu.
PKCE standalone SPA (1st-party, tanpa server session)
Digunakan ketika standalone SPA tidak punya server session untuk diandalkan — model pemegang annual pass.
1. SPA memulai PKCE: GET /oauth/authorize?client_id=...&code_challenge=...
2. Pengguna login di halaman login gateway
3. Gateway redirect balik dengan auth code
4. SPA menukar code:
POST /oauth/token { grant_type: authorization_code, code_verifier, ... }
← { access_token, refresh_token, expires_in }
5. sauth-frontend (pkce mode) menyimpan kedua token di cookie:
tkac_{service} — TTL = 80% dari expires_in
tkrf_{service} — TTL = refreshTokenTtl (default 7 hari)
6. SPA memanggil resource server dengan Bearer token
7. Saat kedaluwarsa: POST /oauth/token { grant_type: refresh_token, refresh_token: <cookie> }
8. Jika refresh gagal → onAuthFailure() → redirect ke /oauth/authorize (ulangi PKCE)Refresh token ada di sini — tapi tidak di session mode
PKCE mode menyimpan refresh token karena tidak ada server session untuk diandalkan. Refresh token adalah kupon perpanjangan. Kehilangan keduanya → harus autentikasi ulang dari awal.
M2M — PHP service worker
Worker menggunakan trait FetchesClientToken:
POST /oauth/token { grant_type: client_credentials, client_id, client_secret }
← { access_token, expires_in }
Token di-cache di Laravel Cache pada 80% dari expires_in.
Jika dapat 401 dari downstream: forgetClientToken() → ambil ulang sekali.M2M token hanya membawa sid (berupa client_id) — tanpa snm, tanpa fet, tanpa scope. Kepercayaan bersifat struktural: signature yang valid + aud yang benar sudah cukup.
Token Exchange — antar-service dengan konteks pengguna
Ketika SRF perlu memanggil PNR atas nama pengguna manusia:
1. SRF menerima permintaan pengguna (token: aud=srf, sid=user123)
2. SRF meminta GWA untuk menukar:
POST /oauth/token {
grant_type: urn:ietf:params:oauth:grant-type:token-exchange,
subject_token: <token pengguna>,
resource: 'pnr'
}
dengan credential client SRF sendiri
3. GWA menerbitkan delegated token: sid=user123, aud=pnr, act={sub: srf_client_id}
4. SRF memanggil PNR dengan delegated token tersebut
5. PNR melihat pengguna asli (sid) dan tahu SRF adalah perantaranya (act.sub)Token exchange hanya ada di GWA. GWC tidak memiliki endpoint ini.
Developer Eksternal 3p (PKCE + scope)
Flow ini tidak ada di BpmJWT — membutuhkan dukungan scope.
1. Aplikasi 3p memulai PKCE di GWA dengan scope yang diminta
2. Pengguna login dan melihat layar persetujuan (consent screen)
3. Aplikasi 3p menukar code dengan token
Token membawa: sid (user ID), snm, fet (permission pengguna), scope (grant client)
4. Aplikasi 3p memanggil resource server
5. Resource server memeriksa KEDUANYA:
sauth.gate:srf.read → pengguna punya srf.read di fet ✓
sauth.scope:srf.read → client mendapat grant srf.read di scope ✓Keduanya harus lolos — logika AND. Token di mana pengguna punya srf.read tapi client tidak pernah mendapat grant-nya (atau sebaliknya) akan ditolak. Inilah jaminan keamanan yang membuat akses pihak ketiga bisa dilakukan tanpa membocorkan permission internal.
API Key JWT — akses mesin partner jangka panjang
Ini adalah pengganti formal untuk penerbitan long-lived token secara ad-hoc. Sebelumnya tidak ada mekanisme terstruktur — developer menerbitkan token dengan exp tinggi dan berharap yang terbaik.
1. Admin GWA menjalankan: php artisan bpm:sauth:apikey issue
→ diminta: nama aplikasi, aud (service tujuan), scope
→ menerbitkan JWT bertanda tangan: iss=APP_URL, aud=srf, jti=uuid, sid=nama-app,
scope=srf.read, fp='3p', TANPA exp
→ baris audit terenkripsi ditulis ke sauth_jti_tokens di GWA
2. Partner menggunakan JWT sebagai Bearer token — selamanya, atau sampai dicabut
3. Resource server memvalidasi: signature + issuer + aud + scope
+ memeriksa jti di tabel lokal sauth_blacklisted_jti_tokens (cache 5 menit)
4. Jika terjadi kebocoran:
GWA: php artisan bpm:sauth:apikey revoke {jti}
Setiap resource server: php artisan bpm:sauth:jti block {jti}Satu key per service
Setiap API key JWT menarget tepat satu aud. Partner yang butuh akses ke SRF dan PNR mendapat dua key terpisah.
Pencabutan bersifat per resource server — desinkronisasi yang disengaja
Service X bisa memblokir key tanpa mempengaruhi service Y. Library memicu event JtiTokenRevoked ketika key dicabut di GWA. Resource server merespons lewat CLI atau dengan mendaftarkan HTTP listener otomatis.
Dev Mocking
Cara lama (BpmJWT)
- Salin private + public key ke
storage/resource server - Daftarkan debug endpoint
isLocal()yang menerbitkan token - Default permission ke
'wm'jika tidak diisi - Daftar issuer yang dipercaya di local mode mencakup semua kode service — service mana pun bisa menerbitkan token yang lolos di mana pun
Cara baru (sauth)
Set SAUTH_BYPASS di .env. Tanpa key, tanpa endpoint, tanpa jebakan default.
# .env (hanya lokal — diabaikan di production)
SAUTH_BYPASS=true
SAUTH_FAKE_CLAIMS_FILE=sauth-fake-claims.jsonPublikasikan template fake claims:
php artisan bpm:sauth:fake publishEdit storage/sauth-fake-claims.json sesuai bentuk pengguna yang dibutuhkan:
{
"1p_user": {
"sid": "user-123",
"snm": "Dev User",
"fet": ["user.read", "report.view"],
"fp": "1p"
},
"1p_m2m": {
"sid": "srf-worker",
"fp": "1p"
},
"3p_m2m": {
"sid": "partner-app",
"scope": ["srf.read"],
"fp": "3p"
}
}SAUTH_BYPASS adalah multi-value enum — gunakan level bypass minimum yang diperlukan:
| Nilai | Signature | Cek fet | Cek scope |
|---|---|---|---|
false/none | diberlakukan | diberlakukan | diberlakukan |
sig | dilewati | diberlakukan | diberlakukan |
fet | dilewati | dilewati | diberlakukan |
scope | dilewati | diberlakukan | dilewati |
perm | dilewati | dilewati | dilewati |
true/total | dilewati | dilewati | dilewati |
Tidak pernah bypass di production
Konfigurasi bypass diabaikan sepenuhnya ketika app()->isLocal() bernilai false, tidak peduli apa nilai SAUTH_BYPASS.
Referensi Middleware
| Middleware | Tujuan | Jenis token yang diterima |
|---|---|---|
sauth.gate | Validasi JWT + cek permission fet | 1p user, 3p user, 1p M2M (tanpa params saja) |
sauth.wm | Mewajibkan fet === 'wm' persis | 1p/3p user dengan level webmaster |
sauth.dual | Session-first, JWT sebagai fallback | Sama dengan sauth.gate |
sauth.scope | Memeriksa claim scope | 3p user, 3p M2M, API key JWT |
sauth.m2m | Menerima M2M apa pun, menolak user token | 1p M2M, 3p M2M, API key JWT |
Tabel keputusan lengkap
| Middleware | 1p user token | 3p user token | 1p M2M | 3p M2M / API key JWT |
|---|---|---|---|---|
sauth.gate | Lolos jika fet cocok (atau wm bypass) | Lolos jika fet cocok (atau wm bypass) | Lolos hanya jika tanpa params | Ditolak (tidak ada fet) |
sauth.wm | Lolos jika fet === 'wm' persis | Lolos jika fet === 'wm' persis | Ditolak | Ditolak |
sauth.dual | Lolos (session atau JWT) | Lolos (session atau JWT) | Lolos hanya jika tanpa params | Ditolak |
sauth.scope | Ditolak (tidak ada scope) | Lolos jika scope cocok (OR) | Ditolak (tidak ada scope) | Lolos jika scope cocok (OR) |
sauth.m2m | Ditolak (snm ada) | Ditolak (snm ada) | Selalu lolos | Lolos jika semua scope ada (AND) |
Endpoint 3p user butuh kedua middleware
// Logika AND — keduanya harus lolos
Route::middleware(['sauth.gate:user.read', 'sauth.scope:srf.read'])->group(function () {
// pengguna harus punya user.read di fet DAN client harus punya grant srf.read di scope
});Arah wildcard permission
Pattern dibangun dari entry fet pengguna dan diuji terhadap permission yang diperlukan — bukan sebaliknya.
pengguna punya fet: ['user.*'] diperlukan: 'user.read'
→ pattern /^user\..*$/ → cocok ✓ (permission luas lolos syarat yang sempit)
pengguna punya fet: ['user.read'] diperlukan: 'user.*'
→ pattern /^user\.read$/ → tidak cocok ✗ (permission sempit tidak bisa lolos syarat yang luas)Aturan ini identik di sauth-client PHP (hasAnyPermission()) dan sauth-frontend TypeScript (permissionCheck()).
Keputusan Desain Utama
Token adalah JWT bertanda tangan sungguhan, bukan opaque Passport token. lcobucci/jwt membangun dan menandatangani token di dalam AccessToken::toString(). Resource server memverifikasi signature secara kriptografis — tanpa DB lookup saat validasi.
Session mode tidak punya refresh token. Gateway session adalah credential jangka panjang. Menerbitkan ulang dari ticketbooth itu murah. Refresh token yang bisa dibaca JavaScript menambah permukaan serangan tanpa manfaat. PKCE mode menggunakan refresh token karena tidak ada server session untuk diandalkan.
Aturan 80% TTL berlaku di mana-mana. FetchesClientToken (PHP M2M) dan RequestInstance (browser) keduanya meng-cache access token pada 80% dari expires_in. Token diperbarui sebelum kedaluwarsa.
Claim fp membuat pihak (party) menjadi eksplisit. Semua token yang diterbitkan sauth-server v0.3+ membawa fp: '1p' atau fp: '3p'. Untuk token lama (tanpa fp), kedua library jatuh kembali ke inferensi dari snm/scope. Gunakan isM2M() (bukan isFirstParty()) untuk membedakan pengguna manusia dari machine client — isFirstParty() mengembalikan true untuk 1p user token sejak v0.3.
ServiceCodes hanya berisi invariant ekosistem. Kode issuer (gwa, gwc) dan kode role (wm, psn, cpy, mit) tempatnya di library. Kode audience service (srf, pnr, pel) tidak — setiap service tahu kodenya sendiri dari config. Hardcoding di library memaksa version bump setiap kali service baru ditambahkan.
Urutan deployment penting untuk v0.3 (perubahan iss 3p). Token 1p tetap menggunakan kode singkat sebagai iss. Token 3p menggunakan APP_URL sebagai iss mulai v0.3. sauth-client v0.3 harus sudah terpasang di semua resource server sebelum sauth-server v0.3 di-deploy ke gateway. Urutan terbalik menyebabkan UnknownIssuerException pada token 3p.
Versioning
Kosakata claim adalah kontrak bersama. Mengganti nama claim di sauth-server akan langsung merusak validasi sauth-client dan TypeScript types di sauth-frontend secara bersamaan. Ketiga library harus version-bump bersama ketika nama claim, nama exception class, atau kode issuer berubah.
| Library | Registry |
|---|---|
bpmlib/sauth-server | Private Satis (Composer) |
bpmlib/sauth-client | Private Satis (Composer) |
@bpmlib/sauth-frontend | Private npm registry |
Untuk pengembangan lokal: gunakan entry "type": "path" di composer.json; gunakan file: path atau npm link untuk npm package.