D
Engineering

Auth & Security

Auth Konco — multi-guard (member default, admin) + Google 2FA. Handshake antar-service via RSA 2048-bit SHA-256 signature + Passport Bearer Token. Verifikasi signature di ApplicationController::token (bukan middleware). Auto-refresh 401 retry loop.

Konco punya dua lapisan auth: end-user auth (member/admin via session + 2FA) dan inter-service auth (dazo-whitelist ↔ dazo-whitelist-api via RSA signature + Passport Bearer). Keduanya berjalan independen — end-user tidak pernah melihat Passport token API engine.

End-user auth (web app)

Multi-guard

GuardDriverProviderMiddlewareTarget
member (default)sessionusers (Eloquent User)auth/cekAuth:memberMember Portal (/member/*)
adminsessionusers (Eloquent User)auth:adminAdmin Portal (/admin/*)

Kedua guard memakai tabel users yang sama; pembedanya role (Spatie) + guard config. Config di config/auth.php:

php
'defaults' => ['guard' => 'member', 'passwords' => 'users'],
'guards' => [
    'member' => ['driver' => 'session', 'provider' => 'users'],
    'admin'  => ['driver' => 'session', 'provider' => 'users'],
],
'providers' => [
    'users' => ['driver' => 'eloquent', 'model' => App\Models\User::class],
],

Google 2FA

AspekKeterangan
Packagepragmarx/google2fa-laravel ^2.2
Middleware alias2fa (app/Http/Middleware/TwoFactorAuthentication.php)
Wajib untukGuard admin & member — semua route di dalam grup auth:admin, 2fa atau auth, 2fa
TOTPTime-based One-Time Password via Authenticator app (Google Authenticator, Authy, dll)
Secret storageTabel UserAuthenticator (relasi User ↔ UserAuthenticator)
Verify routeGET /2fa/verify — tanpa middleware 2fa, agar user bisa submit kode

RBAC (Spatie)

AspekKeterangan
Packagespatie/laravel-permission ^5.9
ModelRole, Permission (app/Models/)
Middlewarepermission:<name> (e.g. permission:user-R, permission:user-CUD)
Permission pattern<resource>-<action> — e.g. user-R (read), user-CUD (create/update/delete)
Seederdatabase/seeders/ — seed roles & permissions awal
Helperapp/helper/role_helper.php — utility cek role/permission

Permission diterapkan granular per route, contoh di routes/_admin.php:

php
$p = 'permission:user-';
$class = UserController::class;
Route::get('/', [$class, 'index'])->name('index')->middleware($p . 'R');
Route::post('/', [$class, 'store'])->name('store')->middleware($p . 'CUD');

Inter-service auth (RSA + Passport)

Komunikasi dazo-whitelist → dazo-whitelist-api memakai dua lapisan:

  1. RSA 2048-bit signature — membuktikan identitas klien
  2. Passport OAuth2 Bearer Token — access token tervalidasi (2 jam TTL)

Handshake flow

text
dazo-whitelist (client)                        dazo-whitelist-api (server)
───────────────────────                        ─────────────────────────
1. Baca API_KEY, API_SECRET, API_PRIVATE       ┐
   dari .env & storage/rsa_keys/                │
                                                │
2. Buat timestamp ISO8601 (now)                │
   $data = implode('|', [                       │
       $client_key,    // API_KEY (UUID app)    │
       $private_key,   // API_PRIVATE           │
       $client_secret, // API_SECRET            │
       $timestamp      // ISO8601               │
   ]);                                          │
                                                │
3. openssl_sign($data, $sig, private_key.pem,   │
                OPENSSL_ALGO_SHA256)             │
   $signature = base64_encode($sig)            │
                                                │
4. POST /api/application/token                  │
   Headers:                                     │
     x-client-key: API_KEY (UUID)               │
     x-timestamp: 2026-08-21T10:26:52+07:00    │
     x-signature: base64(...)                   ├──► 5. Validate signature
                                                      │
                                                      │ $sign = implode('|', [
                                                      │     $e->id,          // UUID app
                                                      │     $e->private_key, // dari DB
                                                      │     $e->secret,      // dari DB
                                                      │     $data['x-timestamp']
                                                      │ ]);
                                                      │
                                                      │ openssl_verify($sign,
                                                      │   base64_decode($x-signature),
                                                      │   $e->public_key,
                                                      │   OPENSSL_ALGO_SHA256)
                                                      │
                                                      │ 6. Cek timestamp ±10 menit
                                                      │    (toleransi replay)
                                                      │
                                                      │ 7. Issue Passport token (2 jam)
                                                      │    $e->tokens()->delete() // revoke old
                                                      │    $token = $e->createToken(...)
                                                      │
                 ◄─────────────────────────────────────┤
8. Simpan token ke tabel `configs`                 │   return &#123; access_token: ... &#125;
   (kolom `api_token`)                             │

Payload signature (penting!)

Payload aktual (ApplicationController::token line 92):

php
$sign = implode('|', [$e->id, $e->private_key, $e->secret, $data['x-timestamp']]);
ElemenSumber (client)Sumber (server)
$e->idAPI_KEY di .env (client)Kolom id tabel applications
$e->private_keyAPI_PRIVATE di .env (client)Kolom private_key tabel applications
$e->secretAPI_SECRET di .env (client)Kolom secret tabel applications
x-timestampnow()->format('Y-m-d\TH:i:sP')Header x-timestamp

Verifikasi di controller, bukan middleware

LayerClassFungsi
Route middleware myapiapp/Http/Middleware/MyApi.phpCek header public-key vs auth()->user()->public_key — simple equality check
Route middleware auth:apiLaravel Passport defaultValidasi Bearer token
Controller ApplicationController::tokenapp/Http/Controllers/ApplicationController.php:62-110RSA verify signature + timestamp ±10 min + issue Passport token
Controller ApplicationController::checkline 18-60RSA verify signature untuk endpoint info aplikasi

Timestamp toleransi

php
$timestamp = Carbon::createFromFormat('Y-m-d\TH:i:sP', $data['x-timestamp']);
$now = Carbon::now();

if (!$timestamp->between($now->copy()->subMinute(10), $now)) &#123;
    return $this->error_response(400, 'Invalid Timestamp');
&#125;

Toleransi 10 menit — mencegah replay attack. Request dengan timestamp di luar rentang now-10min ... now ditolak.

Passport token lifecycle

AspekKeterangan
TTL2 jam (Passport::personalAccessTokensExpireIn(now()->addHours(2)))
Storage (client)Tabel configs (kolom api_token) di dazo-whitelist
RevocationToken lama dihapus saat issue baru: $e->tokens()->delete()
Grant typePersonal Access Token (bukan password grant)

401 auto-refresh retry loop

App\Src\MyApi::request menangkap respon 401 dan otomatis refresh token, lalu retry request:

php
// MyApi.php (disederhanakan)
static function request($path, $params, $method = 'GET', $loop = 0) &#123;
    // ... kirim request dengan Bearer token dari tabel configs ...

    // Tangkap 401 — hanya retry sekali ($loop === 0)
    if ($response->getStatusCode() == 401 && !$loop && isset($params['headers']['Authorization'])) &#123;
        $token = static::get_token();           // handshake ulang
        if ($token['error'] ?? null) return $token;
        $params['headers']['Authorization'] = 'Bearer ' . $token['token'];
        return static::request($path, $params, $method, 1);   // retry dengan $loop=1
    &#125;
&#125;

RSA key management

Penyimpanan key pair

text
dazo-whitelist/
└── storage/rsa_keys/
    ├── private_key.pem    # PEM format, RSA 2048-bit — dipakai openssl_sign
    ├── public_key.pem     # PEM format — dikirim ke API engine, dipakai openssl_verify
    └── .gitignore         # wajib gitignored

Generate & register

bash
# Generate key pair saja
php artisan rsa_key

# Generate + daftar ke API engine + update .env sekaligus
php artisan app:register-client --name=konco-main

RegisterClientApp (app/Console/Commands/RegisterClientApp.php):

  1. Generate RSA 2048-bit key pair via OpenSSL
  2. Simpan ke storage/rsa_keys/private_key.pem & public_key.pem
  3. Create record Application di database dazo-whitelist-api:
    • id = UUID
    • secret = bin2hex(openssl_random_pseudo_bytes(15)) (30 char hex)
    • public_key = isi public_key.pem
  4. Update .env dazo-whitelist:
    • API_KEY = Application::id
    • API_SECRET = Application::secret
    • API_PRIVATE = Application::private_key (juga disimpan ke DB)
    • API_PUBLIC = isi public_key.pem

Yang TIDAK boleh diubah tanpa koordinasi

AreaLokasiDampak
Payload signature urutanMyApi::get_token (client) ↔ ApplicationController::token (server)Signature mismatch → 401 di semua request
Algoritma signOPENSSL_ALGO_SHA256 (kedua sisi)Signature invalid
Headers handshakex-client-key, x-timestamp, x-signature, public-key, AuthorizationRequest ditolak
Passport TTLPassport::personalAccessTokensExpireIn(now()->addHours(2))Bila diperpendek, refresh lebih sering; bila diperpanjang, token expired tetap dipakai
Timestamp formatY-m-d\TH:i:sP (ISO8601 with timezone)Validation gagal
Timestamp toleransisubMinute(10)Bila diubah, replay window berubah

Langkah berikutnya