Skip to content

sauth-client

Middleware dan guard Laravel untuk verifikasi JWT di microservice — tanpa Passport, tanpa database.

Versi: 0.1.3 (latest)

PHPLaravel


TL;DR

Library ini memvalidasi JWT yang diterbitkan oleh gateway (GWA atau GWC) di setiap request masuk ke microservice. Guard sauth me-resolve token dari header Authorization: Bearer, memverifikasi signature dengan public key issuer, lalu mengisi auth()->user() dengan objek MicroserviceUser — tidak ada session, tidak ada database.

Namespace:

php
use Bpmlib\SauthClient\Auth\MicroserviceUser;
use Bpmlib\SauthClient\Auth\MicroserviceTokenGuard;
use Bpmlib\SauthClient\Contracts\IssuerResolverInterface;
use Bpmlib\SauthClient\Resolvers\ConfigDrivenIssuerResolver;
use Bpmlib\SauthClient\Exceptions\InvalidTokenException;
use Bpmlib\SauthClient\Exceptions\TokenExpiredException;
use Bpmlib\SauthClient\Exceptions\UnknownIssuerException;
use Bpmlib\SauthClient\Constants\ServiceCodes;

Konfigurasi:


Installation & Setup

Requirements

PHP: 8.4+

Composer Dependencies:

  • firebase/php-jwt: ^7.0
  • illuminate/auth: ^12.0|^13.0
  • illuminate/support: ^12.0|^13.0
  • illuminate/http: ^12.0|^13.0

Framework: Laravel 12.0+

Composer Install

bash
composer require bpmlib/sauth-client

Auto-Discovery

Service provider otomatis terdaftar via Laravel package discovery.

Publish Commands

bash
php artisan vendor:publish --tag=sauth-client-config

Untuk konfigurasi lengkap, lihat Configuration.


Quick Start

Basic Usage

Daftarkan guard sauth di config/auth.php, lalu pakai middleware sauth.gate di route.

php
// config/auth.php
'guards' => [
    'api' => ['driver' => 'sauth'],
],
php
// routes/api.php
use Illuminate\Support\Facades\Route;

Route::middleware('sauth.gate')->group(function () {
    Route::get('/profile', fn() => auth()->user()->name);
});
php
// Di controller
$user = auth()->user(); // MicroserviceUser
$user->name;            // nama aktor
$user->permissions;     // 'wm' atau array permission

Comprehensive Example

Controller yang menangani user token dan M2M token sekaligus.

php
<?php

namespace App\Http\Controllers;

use Bpmlib\SauthClient\Auth\MicroserviceUser;
use Illuminate\Http\JsonResponse;

class ReportController extends Controller
{
    public function store(): JsonResponse
    {
        /** @var MicroserviceUser $user */
        $user = auth()->user();

        if ($user->isM2M()) {
            // Request dari service lain (client_credentials token)
            $callerId = $user->clientId();

            return response()->json([
                'created_by_service' => $callerId,
            ]);
        }

        // Request dari user manusia
        $userId = $user->userId();

        return response()->json([
            'created_by' => $userId,
            'name'       => $user->name,
        ]);
    }
}

Configuration

Config File

bash
php artisan vendor:publish --tag=sauth-client-config

Menghasilkan config/sauth-client.php.

Available Options

OptionTypeDefaultDescription
trusted_issuersarrayMap issuer code → inline PEM string. Digunakan sebagai fallback jika trusted_issuer_files untuk issuer tersebut tidak diisi.
trusted_issuers.gwastringenv('GWA_PUBLIC_KEY')Inline public key GWA (PEM)
trusted_issuers.gwcstringenv('GWC_PUBLIC_KEY')Inline public key GWC (PEM)
trusted_issuer_filesarrayMap issuer code → path file relatif terhadap storage_path(). Prioritas lebih tinggi dari trusted_issuers untuk issuer yang sama. Direkomendasikan untuk production dan rotasi key.
trusted_issuer_files.gwastring|nullenv('GWA_PUBLIC_KEY_FILE')Path file public key GWA, mis. keys/gwa_public.pem
trusted_issuer_files.gwcstring|nullenv('GWC_PUBLIC_KEY_FILE')Path file public key GWC, mis. keys/gwc_public.pem
audiencestringenv('SAUTH_AUDIENCE')Kode audience service ini — harus cocok dengan claim aud pada token masuk
algorithmsarray[env('SAUTH_ALGO', 'RS256')]Algoritma JWT yang diterima — harus sinkron dengan SAUTH_ALGO di gateway.
bypassboolfalseBypass lokal: lewati verifikasi signature/expiry/audience tapi tetap decode token nyata ke MicroserviceUser. Authorization header tetap wajib kecuali bypass_fake_user.sid diisi. Hanya aktif saat app()->isLocal().
bypass_fake_userarrayFake user yang diinjeksikan saat bypass=true dan tidak ada Authorization header. Diabaikan saat bypass=false.
bypass_fake_user.sidstring|nullenv('SAUTH_FAKE_SID', null)ID fake user — null berarti tetap wajib token nyata (meski tidak diverifikasi)
bypass_fake_user.snmstring|nullenv('SAUTH_FAKE_SNM', 'Dev User')Nama fake user — set null/kosong untuk simulasi M2M token
bypass_fake_user.fetstring|arrayenv('SAUTH_FAKE_FET', 'wm')Permission fake user — default 'wm' agar lolos semua sauth.gate permission check
bypass_fake_m2marrayFake 1p M2M user untuk route sauth.m2m saat bypass=true dan tidak ada Authorization header.
bypass_fake_m2m.sidstring|nullenv('SAUTH_FAKE_M2M_SID', null)client_id fake caller — null berarti tetap wajib token nyata
bypass_fake_m2m.fetarray[]Shape 1p M2M — array kosong, tidak ada snm

Environment Variables

dotenv
# Pilihan 1 — file path (direkomendasikan untuk production)
# Resolver membaca dari storage_path($path); menang atas GWA_PUBLIC_KEY jika diisi.
GWA_PUBLIC_KEY_FILE=keys/gwa_public.pem
GWC_PUBLIC_KEY_FILE=keys/gwc_public.pem

# Pilihan 2 — inline PEM (fallback, atau untuk dev lokal)
GWA_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n..."
GWC_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n..."

SAUTH_AUDIENCE=srf
SAUTH_ALGO=RS256
SAUTH_BYPASS=false
SAUTH_FAKE_SID=local-dev-user
SAUTH_FAKE_SNM="Dev User"
SAUTH_FAKE_FET=wm
SAUTH_FAKE_M2M_SID=local-worker-client

Setiap gateway memiliki key pair sendiri. Public key GWA dan GWC adalah key yang berbeda — diperoleh dari bpm:sauth:init masing-masing gateway. Setelah rotasi key (bpm:sauth:init --force), cukup ganti file di storage/keys/ — tidak perlu mengubah .env.


Core Concepts

Token Claim Structure

Semua token yang diterbitkan gateway mengikuti struktur claim berikut:

ClaimTypeAda di
issstringSemua token — kode issuer (gwa atau gwc)
audstringSemua token — kode target service (srf, pnr, dll.)
iat / expintSemua token — timestamp standard JWT
sidstringSemua token — user ID (user token) atau client_id (M2M)
snmstringUser token saja — nama aktor; tidak ada di semua M2M token
fetstring|arrayUser token + 1p M2M — "wm" untuk webmaster; array permission/role code; 1p M2M mendapat []
scopestring3p M2M saja — OAuth scope space-separated; tidak ada di user token dan 1p M2M
actobject|nullToken exchange saja — {sub: calling_client_id}; null untuk direct token

Identifikasi tipe token: snm tidak ada = M2M. scope ada = 3p M2M. fet ada = user token atau 1p M2M.

sauth.gate membaca fet. sauth.scope membaca scope. sauth.m2m menerima keduanya (1p + 3p) sambil menolak user token. Pisahkan route sesuai tipe token yang diterima.

User Tokens vs M2M Tokens

Perbedaan utama: claim snm (subject name).

User TokenM2M Token
Grant typeauthorization_codeclient_credentials
snmAda (nama user)Tidak ada
sidUser IDClient ID
isM2M()falsetrue
userId()✅ Mengembalikan sid❌ Throws RuntimeException
clientId()❌ Throws RuntimeException✅ Mengembalikan sid

Gunakan isM2M() sebelum memanggil userId() atau clientId(). Keduanya sengaja throw exception jika dipanggil pada tipe token yang salah — gagal cepat lebih baik daripada audit log yang korup.

Permission Model

Klaim fet menentukan akses:

Nilai fetTipeBehaviour di sauth.gate
"wm"stringWebmaster bypass — selalu lolos, tanpa cek permission
["report.read", "report.write"]arrayPermission GWA — dicek dengan OR logic
["psn"]arrayRole code GWC — dicek dengan OR logic

OR logic: sauth.gate:psn,cpy lolos jika fet mengandung psn atau cpy.

Wildcard: permission user module.* cocok dengan module.read, module.write, dll. Matching menggunakan regex wildcard.

Per-Gateway Public Keys

Setiap gateway menggunakan key pair RSA sendiri. Claim iss pada token menentukan public key mana yang dipakai untuk verifikasi. Artinya kompromi pada satu gateway tidak mempengaruhi token dari gateway lain, dan rotasi key bisa dilakukan per-gateway secara independen.


API Reference

MicroserviceUser

Objek Authenticatable stateless — tidak ada Eloquent, tidak ada DB. Dibuat dari claim JWT yang sudah divalidasi.

Namespace: Bpmlib\SauthClient\Auth\MicroserviceUser

Properties

PropertyTypeDescription
$idstringNilai sid — user ID atau client ID tergantung tipe token
$namestring|nullNilai snm — nama aktor; null untuk M2M token
$permissionsstring|array<int, string>|nullNilai fet'wm' atau array permission/role code; null untuk 3p M2M token (tidak punya fet)
$actorstring|nullNilai act.sub — client ID pemanggil pada delegated token; null untuk direct token
$scopearray<int, string>|nullNilai claim scope yang sudah diparsing — hanya ada di 3p M2M token; null untuk user/1p M2M token.

Semua property readonly.

Methods

isM2M()
php
public function isM2M(): bool

Mengembalikan true jika $name adalah null (token diterbitkan via client_credentials).

userId()
php
public function userId(): string

Mengembalikan $id untuk user token. Throws RuntimeException jika dipanggil pada M2M token.

clientId()
php
public function clientId(): string

Mengembalikan $id untuk M2M token. Throws RuntimeException jika dipanggil pada user token.

isFirstParty()

TIP

NEW v0.1.3

php
public function isFirstParty(): bool

Mengembalikan true jika token adalah 1p M2M — claim fet ada (bahkan []) dan tidak ada scope. Selalu false untuk user token dan 3p M2M.

isThirdParty()

TIP

NEW v0.1.3

php
public function isThirdParty(): bool

Mengembalikan true jika token adalah 3p M2M — claim scope ada dan tidak ada fet. Selalu false untuk user token dan 1p M2M.

hasScope()

TIP

NEW v0.1.3

php
public function hasScope(string ...$scopes): bool

Parameters

NameTypeDefaultDescription
$scopesstring variadicSatu atau lebih scope yang dicek (AND logic)

Returns: booltrue hanya jika semua scope yang disebutkan ada di claim scope token. Selalu false untuk 1p M2M (tidak punya claim scope).

AND logic: hasScope('srf.read', 'srf.write') mengembalikan false jika token hanya punya srf.read.


MicroserviceTokenGuard

Guard stateless yang memvalidasi Bearer JWT di setiap request dan mengembalikan MicroserviceUser.

Namespace: Bpmlib\SauthClient\Auth\MicroserviceTokenGuard
Implements: Illuminate\Contracts\Auth\Guard
Uses: Illuminate\Auth\GuardHelpers

Guard ini dikonfigurasi, bukan dipanggil langsung. Daftarkan di config/auth.php dengan driver 'sauth'.

Methods

user()
php
public function user(): ?MicroserviceUser

Mengekstrak token dari header Authorization: Bearer, memverifikasi signature, dan mengembalikan MicroserviceUser. Mengembalikan null jika tidak ada token. Throws InvalidTokenException, TokenExpiredException, atau UnknownIssuerException jika token invalid.

validate()
php
public function validate(array $credentials = []): bool

Selalu mengembalikan false — stub interface, tidak digunakan.


IssuerResolverInterface

Kontrak untuk me-resolve public key RSA dari kode issuer.

Namespace: Bpmlib\SauthClient\Contracts\IssuerResolverInterface

resolvePublicKey()
php
public function resolvePublicKey(string $issuer): string

Parameters

NameTypeDefaultDescription
$issuerstringKode issuer dari claim iss

Returns: string — RSA public key dalam format PEM

Throws: UnknownIssuerException jika issuer tidak dikenal

Implementasikan interface ini untuk mengambil public key secara dinamis (misalnya dari JWKS endpoint). Lihat selengkapnya.


ConfigDrivenIssuerResolver

Implementasi default IssuerResolverInterface — me-resolve public key dari config/sauth-client.php dengan prioritas dua tahap:

  1. File path (prioritas) — baca trusted_issuer_files.{issuer}; jika diisi, baca file dari storage_path($path).
  2. Inline PEM (fallback) — baca trusted_issuers.{issuer} sebagai string PEM langsung.

Melempar UnknownIssuerException jika keduanya kosong, atau jika path diisi tapi file tidak ditemukan/kosong.

Namespace: Bpmlib\SauthClient\Resolvers\ConfigDrivenIssuerResolver

Terdaftar otomatis sebagai singleton. Tidak perlu diinstansiasi manual kecuali ingin di-override.


Middlewares

ValidateMicroserviceToken

Alias: sauth.gate

Memvalidasi JWT dan secara opsional memeriksa permission.

Signature:

php
public function handle(Request $request, Closure $next, string ...$permissionNeeded): mixed

Parameters

NameTypeDefaultDescription
$permissionNeededstring variadic[]Satu atau lebih permission yang diperlukan (OR logic)

Behaviour:

  • Tanpa params → lolos jika token valid
  • fet === 'wm' → selalu lolos (webmaster bypass)
  • Dengan params → lolos jika fet mengandung salah satu dari params (OR logic, wildcard support)

Responses:

  • 401 — tidak ada token, token invalid/expired, atau issuer tidak dikenal
  • 403 — token valid tapi permission tidak terpenuhi

RequireWebmaster

Alias: sauth.wm

Memvalidasi JWT dan memastikan fet === 'wm'. Menolak token dengan permission array, meskipun signature valid.

Signature:

php
public function handle(Request $request, Closure $next): mixed

Tidak menerima parameter permission. Gunakan middleware ini — bukan sauth.gate:wm — untuk route webmaster.

Responses:

  • 401 — token tidak ada atau invalid
  • 403 — token valid tapi bukan webmaster

DualAuthenticate

Alias: sauth.dual

Cek guard web (session) lebih dulu; jika tidak ada session aktif, delegasi ke ValidateMicroserviceToken.

Signature:

php
public function handle(Request $request, Closure $next, string ...$permissionNeeded): mixed

Parameters

NameTypeDefaultDescription
$permissionNeededstring variadic[]Diteruskan ke ValidateMicroserviceToken jika path JWT yang diambil

Catatan: Pada path session, auth()->user() mengembalikan Eloquent model dari guard web. Pada path JWT, auth('api')->user() mengembalikan MicroserviceUser. Controller pada route dual harus menangani keduanya.


CheckServiceScope

Alias: sauth.scope

Memvalidasi JWT dan memeriksa claim scope (space-separated). Khusus untuk third-party M2M token dari external OAuth client yang mendapat akses via client_credentials grant di GWA. Gunakan sauth.gate untuk user/1p M2M — gunakan sauth.scope untuk 3p M2M.

Signature:

php
public function handle(Request $request, Closure $next, string ...$scopeNeeded): mixed

Parameters

NameTypeDefaultDescription
$scopeNeededstring variadic[]Satu atau lebih scope yang diperlukan (OR logic, exact match)

Behaviour:

  • Tanpa params → lolos jika token valid
  • Dengan params → lolos jika claim scope mengandung salah satu dari params (OR logic)

Responses:

  • 401 — token tidak ada atau invalid
  • 403 — token valid tapi scope tidak terpenuhi

ValidateM2MToken

TIP

NEW v0.1.3

Alias: sauth.m2m

Memvalidasi JWT dan memastikan token adalah M2M — baik 1p internal worker maupun 3p external partner. User token selalu ditolak (403). Scope params hanya berlaku untuk 3p M2M (AND logic).

Signature:

php
public function handle(Request $request, Closure $next, string ...$requiredScopes): Response

Parameters

NameTypeDefaultDescription
$requiredScopesstring variadic[]Scope yang diperlukan — hanya dicek untuk token 3p M2M (AND logic)

Behaviour:

  • User token (snm ada) → 403
  • 1p M2M (isFirstParty()) → selalu lolos, scope params diabaikan
  • 3p M2M (isThirdParty()) tanpa params → lolos
  • 3p M2M dengan params → lolos hanya jika semua scope ada (AND logic)
  • Token malformed (tidak ada fet maupun scope) → 403

Responses:

  • 401 — token tidak ada atau invalid
  • 403 — user token, atau 3p M2M dengan scope tidak cukup

Exceptions

InvalidTokenException

php
new InvalidTokenException(string $reason = 'Invalid token')

Dilempar saat token malformed, signature tidak valid, atau claim aud tidak cocok.


TokenExpiredException

php
new TokenExpiredException()
// Message: 'Token has expired'

Dilempar saat JWT sudah melewati waktu exp.


UnknownIssuerException

php
new UnknownIssuerException(string $issuer)
// Message: "Unknown token issuer: {$issuer}"

Dilempar saat kode iss pada token tidak ada di trusted_issuers.


ServiceCodes

TIP

NEW v0.1.3 — nilai GWA/GWC diubah dari '01-gwa'/'02-gwc' ke 'gwa'/'gwc'

Konstanta ekosistem-wide untuk kode issuer gateway, permission, dan role GWC.

Namespace: Bpmlib\SauthClient\Constants\ServiceCodes

Issuer Codes (claim iss) — hanya gateway issuer ekosistem:

php
ServiceCodes::GWA // 'gwa'
ServiceCodes::GWC // 'gwc'

Permission:

php
ServiceCodes::WEBMASTER // 'wm'

GWC Customer Role Codes (nilai dalam claim fet):

php
ServiceCodes::ROLE_PERSONAL  // 'psn'
ServiceCodes::ROLE_COMPANY   // 'cpy'
ServiceCodes::ROLE_MITRA     // 'mit'
ServiceCodes::CUSTOMER_ROLES // ['psn', 'cpy', 'mit']

Konstanta yang dihapus di v0.1.2:

SRF_ISSUER, SRF, PNR, PEL — ini adalah nilai app-level, bukan kontrak ekosistem. Setiap service sudah tahu audience code-nya sendiri dari SAUTH_AUDIENCE di .env. Definisikan secara lokal di codebase masing-masing jika dibutuhkan:

php
// Di consuming app — bukan di library
const SRF_ISSUER = 'srf';
const MY_AUDIENCE = 'srf';

Examples

Daftar Isi:


1. Proteksi route — token apapun yang valid

Semua route di grup ini memerlukan token JWT yang valid, tanpa syarat permission tertentu.

php
Route::middleware('sauth.gate')->group(function () {
    Route::get('/data',    [DataController::class, 'index']);
    Route::post('/submit', [DataController::class, 'store']);
});

2. Akses berbasis permission (OR logic)

Route ini bisa diakses oleh user dengan permission report.view atau report.export.

php
Route::get('/reports', [ReportController::class, 'index'])
    ->middleware('sauth.gate:report.view,report.export');

// Bisa juga untuk GWC role codes (psn ATAU cpy)
Route::get('/customer-data', [CustomerController::class, 'index'])
    ->middleware('sauth.gate:psn,cpy');

3. Wildcard permission

Permission assessment.* pada token user cocok dengan semua permission di modul assessment.

php
// Route ini dilindungi dengan permission spesifik
Route::post('/assessments', [AssessmentController::class, 'store'])
    ->middleware('sauth.gate:assessment.create');

// Token user dengan fet: ['assessment.*'] akan lolos
// Token user dengan fet: ['assessment.create'] juga lolos
// Token user dengan fet: ['report.view'] akan ditolak (403)

4. Route khusus webmaster

Gunakan sauth.wm, bukan sauth.gate:wm.

php
Route::middleware('sauth.wm')->group(function () {
    Route::get('/admin/config',  [AdminController::class, 'config']);
    Route::post('/admin/reset',  [AdminController::class, 'reset']);
});

sauth.gate:wm tidak akan berfungsi karena 'wm' sebagai string permission tidak akan pernah cocok dengan array fet. sauth.wm secara eksplisit memeriksa fet === 'wm'.


5. Dual authentication (session OR JWT)

Untuk route yang bisa diakses dari Inertia frontend (session) maupun dari service lain (JWT).

php
// Tanpa syarat permission
Route::middleware('sauth.dual')->get('/shared', [SharedController::class, 'index']);

// Dengan syarat permission (hanya berlaku pada path JWT)
Route::middleware('sauth.dual:data.read')->get('/data', [SharedController::class, 'data']);
php
// Di controller
public function index(): JsonResponse
{
    if (auth('web')->check()) {
        // Path session — auth()->user() adalah Eloquent model
        $user = auth('web')->user();
        return response()->json(['source' => 'session', 'name' => $user->name]);
    }

    // Path JWT — auth('api')->user() adalah MicroserviceUser
    $user = auth('api')->user();
    return response()->json(['source' => 'jwt', 'name' => $user->name]);
}

6. Controller: branching user vs M2M

Pola standar untuk endpoint yang bisa dipanggil oleh user maupun service.

php
<?php

namespace App\Http\Controllers;

use Bpmlib\SauthClient\Auth\MicroserviceUser;
use Illuminate\Http\JsonResponse;

class SubmissionController extends Controller
{
    public function store(): JsonResponse
    {
        /** @var MicroserviceUser $actor */
        $actor = auth()->user();

        $record = [
            'data'       => request('payload'),
            'created_at' => now(),
        ];

        if ($actor->isM2M()) {
            // Panggilan antar-service
            $record['created_by_service'] = $actor->clientId();
            $record['delegated_user']     = $actor->actor; // RFC 8693 act.sub, bisa null
        } else {
            // Request dari user manusia
            $record['created_by'] = $actor->userId();
            $record['actor_name'] = $actor->name;
        }

        // Simpan $record ...

        return response()->json($record, 201);
    }
}

7. Custom IssuerResolver

Ganti ConfigDrivenIssuerResolver dengan implementasi sendiri — misalnya untuk mengambil public key dari JWKS endpoint atau secret manager.

php
<?php

namespace App\Auth;

use Bpmlib\SauthClient\Contracts\IssuerResolverInterface;
use Bpmlib\SauthClient\Exceptions\UnknownIssuerException;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;

class RemoteJwksResolver implements IssuerResolverInterface
{
    public function resolvePublicKey(string $issuer): string
    {
        return Cache::remember("jwks_{$issuer}", 3600, function () use ($issuer) {
            $response = Http::get("https://keys.internal/jwks/{$issuer}");

            if ($response->failed()) {
                throw new UnknownIssuerException($issuer);
            }

            return $response->json('public_key')
                ?? throw new UnknownIssuerException($issuer);
        });
    }
}

Daftarkan di AppServiceProvider:

php
use Bpmlib\SauthClient\Contracts\IssuerResolverInterface;
use App\Auth\RemoteJwksResolver;

public function register(): void
{
    $this->app->bind(IssuerResolverInterface::class, RemoteJwksResolver::class);
}

8. External partner route (3p M2M)

Pisahkan route internal (user + 1p M2M) dari route eksternal (3p M2M partner). sauth.gate membaca claim fet; sauth.scope membaca claim scope — keduanya tidak boleh dicampur.

php
Route::prefix('/api')->group(function () {
    // Internal — user token dan 1p M2M worker
    Route::middleware('sauth.gate')->group(function () {
        Route::get('/submissions',  [SubmissionController::class, 'index']);
        Route::post('/submissions', [SubmissionController::class, 'store']);
    });

    // Eksternal — 3p M2M partner dengan OAuth scope
    Route::middleware('sauth.scope:partner.read')->group(function () {
        Route::get('/partner/submissions', [PartnerController::class, 'index']);
    });

    Route::middleware('sauth.scope:partner.read,partner.write')->group(function () {
        Route::post('/partner/submissions', [PartnerController::class, 'store']);
    });
});
php
// Di controller — token 3p M2M selalu M2M, clientId() aman dipanggil langsung
public function index(): JsonResponse
{
    $partner = auth()->user(); // MicroserviceUser

    return response()->json([
        'partner_id' => $partner->clientId(),
        'scope'      => $partner->scope, // ['partner.read', 'partner.write']
    ]);
}

9. Artisan: bpm:sauth:lookup

TIP

NEW v0.1.2

Reverse-index semua route berdasarkan middleware sauth yang dipakai. Berguna untuk audit permission, onboarding, atau CI script.

bash
# Lihat semua route beserta permission yang dibutuhkan (fet)
php artisan bpm:sauth:lookup

# Lihat route berdasarkan OAuth scope (3p M2M)
php artisan bpm:sauth:lookup --type=scope

# Lihat semua route yang dilindungi sauth.wm
php artisan bpm:sauth:lookup --type=wm

# Lihat route sauth.m2m beserta scope yang diperlukan
php artisan bpm:sauth:lookup --type=m2m

# Output JSON — pipeable ke jq
php artisan bpm:sauth:lookup --json
php artisan bpm:sauth:lookup --type=m2m --json | jq '.[0].routes'

Contoh output --type=fet (default):

 Permission    Routes  Route Names
 ------------  ------  ----------------------------------------
 (none)             1  api.health
 cpy                1  api.applications.store
 psn                3  api.profile.show, api.profile.update, api.applications.store
 report.view        3  api.reports.index, api.reports.show, api.reports.export

Route dengan sauth.gate:psn,cpy muncul di baris psn dan cpy. (none) berarti middleware ada tapi tanpa syarat permission — token valid apapun diterima.


10. sauth.m2m — internal worker dan external partner

TIP

NEW v0.1.3

Route yang bisa dipanggil oleh internal worker (1p M2M) maupun external partner (3p M2M), tapi menolak user token. Scope params hanya berlaku untuk 3p.

php
Route::prefix('/api')->group(function () {
    // Hanya M2M — internal worker ATAU external partner
    Route::middleware('sauth.m2m')->group(function () {
        Route::get('/internal/status', [StatusController::class, 'index']);
    });

    // M2M dengan scope — 1p selalu lolos; 3p harus punya srf.read
    Route::middleware('sauth.m2m:srf.read')->group(function () {
        Route::get('/partner/exam-data', [ExamController::class, 'index']);
    });

    // AND logic — 3p harus punya KEDUA scope
    Route::middleware('sauth.m2m:srf.read,srf.write')->group(function () {
        Route::post('/partner/exam-submit', [ExamController::class, 'store']);
    });
});
php
// Di controller — branch berdasarkan subtipe M2M
public function index(): JsonResponse
{
    /** @var MicroserviceUser $caller */
    $caller = auth()->user();

    if ($caller->isFirstParty()) {
        // Internal service worker — trusted sepenuhnya
        return response()->json([
            'caller'   => $caller->clientId(),
            'internal' => true,
        ]);
    }

    // 3p external partner
    return response()->json([
        'partner_id' => $caller->clientId(),
        'scope'      => $caller->scope,
    ]);
}

Laravel Integration

Service Provider

SauthClientServiceProvider terdaftar otomatis via package discovery. Provider ini:

  • Merge config/sauth-client.php
  • Bind IssuerResolverInterfaceConfigDrivenIssuerResolver (singleton)
  • Daftarkan guard driver 'sauth'
  • Daftarkan lima alias middleware
  • Daftarkan Artisan command bpm:sauth:lookup

Guard Registration

Tambahkan di config/auth.php:

php
'guards' => [
    'api' => [
        'driver' => 'sauth',
    ],
],

Setelah ini, seluruh auth API Laravel berfungsi tanpa database:

php
auth()->user()              // MicroserviceUser | null
auth()->check()             // true jika token valid
auth()->user()->isM2M()     // true jika client_credentials token
auth()->user()->userId()    // human user ID
auth()->user()->clientId()  // service client ID

Middleware Aliases

AliasClassKeterangan
sauth.gateValidateMicroserviceTokenValidasi token + permission opsional
sauth.wmRequireWebmasterValidasi token + wajib webmaster
sauth.dualDualAuthenticateSession fallback → JWT
sauth.scopeCheckServiceScopeValidasi token + scope 3p M2M (OR)
sauth.m2mValidateM2MTokenM2M-only; 1p selalu lolos; 3p scope AND

Artisan Commands

CommandKeterangan
bpm:sauth:lookupReverse-index route berdasarkan sauth middleware key (--type=fet|scope|wm|m2m, --json)