kodepik/ums-laravel
Composer 安装命令:
composer require kodepik/ums-laravel
包简介
Laravel SDK for UMS (User Management System) — SSO authentication & authorization
README 文档
README
Laravel package for SSO authentication & authorization via UMS (User Management System).
Package ini menyediakan:
- SSO Login/Logout (OAuth2 redirect flow via UMS → Keycloak)
- JWT token validation via JWKS public key (RS256)
- Middleware untuk protect routes berdasarkan permission, role, dan module
- Helper functions untuk cek akses di mana saja (controller, service, blade)
- API authentication dengan Bearer token
Daftar Isi
- Requirements
- Installation
- Configuration
- Prasyarat di UMS Admin
- Quick Start — SSO Login
- Quick Start — API Bearer Token
- Middleware Reference
- Helper Functions
- UmsClaims Object
- Facade
- Implementasi Lengkap (Step by Step)
- Blade Template
- Advanced: Custom Routes
- Advanced: Token Refresh
- Troubleshooting
- Security Notes
Requirements
- PHP 8.1+
- Laravel 10.x atau 11.x
- UMS server yang sudah running
Installation
1. Install package
composer require kodepik/ums-laravel
Package menggunakan Laravel auto-discovery — ServiceProvider dan Facade otomatis terdaftar.
2. Publish config
php artisan vendor:publish --tag=ums-config
File config/ums.php akan dibuat.
Configuration
Tambahkan ke file .env:
# URL server UMS UMS_BASE_URL=https://ums.yourserver.com # App ID yang didaftarkan di UMS Admin UMS_APP_ID=your-app-id # Callback URL (harus didaftarkan juga di UMS Admin) UMS_CALLBACK_URL=https://yourapp.com/ums/callback # SSL verification (set false jika development dengan self-signed cert) UMS_VERIFY_SSL=true # Optional: disable auto-registered routes UMS_ROUTES_ENABLED=true # Optional: ganti prefix routes (default: 'ums') UMS_ROUTES_PREFIX=ums # Optional: JWKS cache duration (default: 3600 seconds / 1 jam) UMS_JWKS_CACHE_TTL=3600
Prasyarat di UMS Admin
Sebelum integrasi, pastikan di UMS Admin:
- Application sudah dibuat — catat
app_id(contoh:APP-xxxxx) - Callback URL didaftarkan —
https://yourapp.com/ums/callback - Logout redirect URL didaftarkan —
https://yourapp.com/login - User di-assign ke application — user yang bisa login
- Module, Role, Permission — sudah di-assign ke user sesuai kebutuhan
Quick Start — SSO Login
Setelah install dan config, SSO langsung bisa dipakai tanpa coding tambahan.
Auto-registered routes:
| Route | Method | Name | Fungsi |
|---|---|---|---|
/ums/login |
GET | ums.login |
Redirect ke UMS → Keycloak |
/ums/callback |
GET | ums.callback |
Handle callback setelah login |
/ums/logout |
GET | ums.logout |
Logout + redirect ke UMS logout |
/ums/refresh |
POST | ums.refresh |
Refresh token yang expired |
Di halaman login kamu:
<a href="{{ route('ums.login') }}">Login with SSO</a>
Setelah login berhasil:
User di-redirect ke /dashboard (configurable di UmsAuthController). Data tersimpan di session:
session('ums_token'); // JWT access token session('ums_refresh_token'); // Refresh token session('ums_user'); // Decoded claims (array)
Logout:
<a href="{{ route('ums.logout') }}">Logout</a>
Quick Start — API Bearer Token
Untuk API endpoint yang di-consume frontend/mobile, gunakan Bearer token:
// routes/api.php Route::middleware(['ums.auth'])->group(function () { Route::get('/profile', function () { return response()->json(ums_user()->toArray()); }); });
Client mengirim request dengan header:
Authorization: Bearer <jwt_token_dari_ums>
Middleware Reference
ums.auth — Validasi Token
Memastikan request memiliki Bearer token yang valid.
Route::middleware(['ums.auth'])->group(function () { // Semua route di sini butuh token valid });
Response jika gagal:
{"success": false, "message": "Missing authorization token"} // 401
{"success": false, "message": "Invalid or expired token"} // 401
ums.permission:module,permission — Cek Permission
Memastikan user punya permission tertentu di module tertentu.
// User harus punya permission 'read' di module 'inventory' Route::middleware(['ums.auth', 'ums.permission:inventory,read'])->group(function () { Route::get('/items', [ItemController::class, 'index']); }); // User harus punya permission 'write' di module 'inventory' Route::middleware(['ums.auth', 'ums.permission:inventory,write'])->group(function () { Route::post('/items', [ItemController::class, 'store']); });
Response jika gagal:
{"success": false, "message": "Permission denied: inventory.write"} // 403
ums.role:module,role — Cek Role
Memastikan user punya role tertentu di module tertentu.
// User harus punya role 'admin' di module 'dashboard' Route::middleware(['ums.auth', 'ums.role:dashboard,admin'])->group(function () { Route::get('/admin', [AdminController::class, 'index']); });
Response jika gagal:
{"success": false, "message": "Role denied: dashboard.admin"} // 403
ums.module:module — Cek Akses Module
Memastikan user punya akses ke module tertentu (tanpa cek permission/role spesifik).
// User harus punya akses ke module 'reports' Route::middleware(['ums.auth', 'ums.module:reports'])->group(function () { Route::get('/reports', [ReportController::class, 'index']); });
Response jika gagal:
{"success": false, "message": "Module access denied: reports"} // 403
Kombinasi Middleware
Middleware bisa di-chain:
// Harus login + punya module inventory + permission delete Route::middleware(['ums.auth', 'ums.module:inventory', 'ums.permission:inventory,delete']) ->delete('/items/{id}', [ItemController::class, 'destroy']);
Helper Functions
Tersedia global helper yang bisa dipanggil dari mana saja:
// Get current user (dari request attribute atau session) $user = ums_user(); // UmsClaims|null // Cek permission ums_can('inventory', 'read'); // bool ums_can('inventory', 'delete'); // bool // Cek role ums_has_role('dashboard', 'admin'); // bool ums_has_role('dashboard', 'viewer'); // bool // Cek module access ums_has_module('inventory'); // bool ums_has_module('reports'); // bool // Get raw JWT token dari session ums_token(); // string|null
UmsClaims Object
Object yang dikembalikan oleh ums_user():
$user = ums_user(); // Properties $user->userId; // "d4c8546e-3166-4afe-a79f-70c073a7c1f7" $user->email; // "user@example.com" $user->appId; // "APP-10cd66bc" $user->modules; // array of module claims $user->exp; // 1782881536 (token expiry unix timestamp) $user->iat; // 1782877936 (token issued at) $user->jti; // "e2a4b879-ffd9-4a1a-9536-f0edcb1a9ebb" // Methods $user->hasPermission('inventory', 'read'); // bool $user->hasRole('inventory', 'admin'); // bool $user->hasModule('inventory'); // bool $user->getModuleRoles('inventory'); // ['admin', 'editor'] $user->getModulePermissions('inventory'); // ['read', 'write', 'delete'] $user->getModuleNames(); // ['dashboard', 'inventory'] $user->isExpired(); // bool $user->toArray(); // array
Struktur modules:
$user->modules = [ [ 'module' => 'dashboard', 'parent_module' => null, 'roles' => ['admin'], 'permissions' => ['create', 'delete', 'update', 'view'], ], [ 'module' => 'reports', 'parent_module' => 'dashboard', 'roles' => ['viewer'], 'permissions' => ['read'], ], ];
Facade
use Kodepik\UMS\Facades\UMS; // Validate token secara manual $claims = UMS::validateToken($jwtToken); // Validate via UMS server (server-side, lebih aman) $claims = UMS::validateTokenRemote($jwtToken); // Refresh token $result = UMS::refreshToken($refreshToken); // $result = ['token' => '...', 'refresh_token' => '...'] // Get URLs $loginUrl = UMS::getLoginUrl(); $logoutUrl = UMS::getLogoutUrl('https://myapp.com/login'); // Clear JWKS cache (setelah key rotation di UMS) UMS::invalidateJwksCache();
Implementasi Lengkap (Step by Step)
Step 1: Install & Config
composer require kodepik/ums-laravel php artisan vendor:publish --tag=ums-config
Tambahkan ke .env:
UMS_BASE_URL=https://ums.yourserver.com UMS_APP_ID=APP-xxxxx UMS_CALLBACK_URL=http://localhost:8000/ums/callback UMS_VERIFY_SSL=false
Step 2: Buat Login Page
{{-- resources/views/login.blade.php --}} <a href="{{ route('ums.login') }}">Login with UMS SSO</a>
Step 3: Buat Dashboard (Protected Page)
// routes/web.php Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
// app/Http/Controllers/DashboardController.php <?php namespace App\Http\Controllers; class DashboardController extends Controller { public function index() { $user = ums_user(); if (!$user) { return redirect()->route('ums.login'); } return view('dashboard', compact('user')); } }
{{-- resources/views/dashboard.blade.php --}} <h1>Welcome, {{ ums_user()->email }}</h1> <p>Your modules: {{ implode(', ', ums_user()->getModuleNames()) }}</p> <a href="{{ route('ums.logout') }}">Logout</a>
Step 4: Protect API Routes
// routes/api.php use App\Http\Controllers\Api\ItemController; Route::middleware(['ums.auth'])->group(function () { Route::get('/me', function () { return response()->json(['data' => ums_user()->toArray()]); }); // CRUD dengan permission checks Route::middleware(['ums.permission:inventory,read']) ->get('/items', [ItemController::class, 'index']); Route::middleware(['ums.permission:inventory,create']) ->post('/items', [ItemController::class, 'store']); Route::middleware(['ums.permission:inventory,update']) ->put('/items/{id}', [ItemController::class, 'update']); Route::middleware(['ums.permission:inventory,delete']) ->delete('/items/{id}', [ItemController::class, 'destroy']); // Admin-only Route::middleware(['ums.role:inventory,admin']) ->post('/items/bulk-delete', [ItemController::class, 'bulkDelete']); });
Step 5: Controller dengan Middleware
<?php namespace App\Http\Controllers\Api; use App\Http\Controllers\Controller; use Illuminate\Http\Request; class ItemController extends Controller { public function __construct() { $this->middleware('ums.auth'); $this->middleware('ums.permission:inventory,read')->only(['index', 'show']); $this->middleware('ums.permission:inventory,create')->only(['store']); $this->middleware('ums.permission:inventory,update')->only(['update']); $this->middleware('ums.permission:inventory,delete')->only(['destroy']); } public function index() { $items = \App\Models\Item::all(); return response()->json(['success' => true, 'data' => $items]); } public function store(Request $request) { $validated = $request->validate(['name' => 'required|string']); $item = \App\Models\Item::create([ ...$validated, 'created_by' => ums_user()->email, ]); return response()->json(['success' => true, 'data' => $item], 201); } public function show(string $id) { $item = \App\Models\Item::findOrFail($id); return response()->json(['success' => true, 'data' => $item]); } public function update(Request $request, string $id) { $item = \App\Models\Item::findOrFail($id); $item->update($request->validated()); return response()->json(['success' => true, 'data' => $item]); } public function destroy(string $id) { \App\Models\Item::findOrFail($id)->delete(); return response()->json(['success' => true, 'message' => 'Deleted']); } }
Step 6: Conditional Logic di Service/Controller
// Tanpa middleware — cek manual di logic public function exportReport() { if (!ums_can('reports', 'export')) { abort(403, 'You do not have export permission'); } // ... generate export } // Conditional content berdasarkan role public function dashboard() { $data = ['items' => Item::all()]; if (ums_has_role('dashboard', 'admin')) { $data['admin_stats'] = $this->getAdminStats(); } return view('dashboard', $data); }
Blade Template
Gunakan helper functions langsung di Blade:
{{-- Tampilkan info user --}} @if(ums_user()) <span>Logged in as: {{ ums_user()->email }}</span> @endif {{-- Conditional berdasarkan permission --}} @if(ums_can('inventory', 'create')) <button>+ Add Item</button> @endif @if(ums_can('inventory', 'delete')) <button class="btn-danger">Delete Selected</button> @endif {{-- Conditional berdasarkan role --}} @if(ums_has_role('dashboard', 'admin')) <a href="/admin">Admin Panel</a> @endif {{-- Conditional berdasarkan module --}} @if(ums_has_module('reports')) <a href="/reports">Reports</a> @endif {{-- Show/hide menu items --}} <nav> <a href="/dashboard">Dashboard</a> @if(ums_has_module('inventory')) <a href="/inventory">Inventory</a> @endif @if(ums_has_module('reports')) <a href="/reports">Reports</a> @endif @if(ums_has_role('dashboard', 'admin')) <a href="/settings">Settings</a> @endif </nav>
Advanced: Custom Routes
Jika ingin handle sendiri tanpa auto-registered routes:
UMS_ROUTES_ENABLED=false
Lalu buat sendiri:
// routes/web.php use Kodepik\UMS\Services\UmsClient; Route::get('/auth/login', function (UmsClient $ums) { return redirect()->away($ums->getLoginUrl()); }); Route::get('/auth/callback', function (Request $request, UmsClient $ums) { $token = $request->query('token'); $claims = $ums->validateToken($token); session([ 'ums_token' => $token, 'ums_refresh_token' => $request->query('refresh_token'), 'ums_user' => $claims->toArray(), ]); return redirect('/dashboard'); }); Route::get('/auth/logout', function (UmsClient $ums) { session()->flush(); return redirect()->away($ums->getLogoutUrl(url('/login'))); });
Advanced: Token Refresh
Otomatis (via endpoint)
SDK menyediakan POST /ums/refresh yang bisa dipanggil dari frontend:
// Panggil sebelum token expired const response = await fetch('/ums/refresh', { method: 'POST', headers: { 'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').content, }, }); const data = await response.json(); // data.data.token = new access token
Manual di backend
use Kodepik\UMS\Facades\UMS; $refreshToken = session('ums_refresh_token'); $result = UMS::refreshToken($refreshToken); session([ 'ums_token' => $result['token'], 'ums_refresh_token' => $result['refresh_token'], ]);
Troubleshooting
| Problem | Penyebab | Solusi |
|---|---|---|
"kid" empty, unable to lookup correct key |
UMS JWKS tidak memiliki kid field |
SDK sudah handle otomatis (v1.0+) |
Invalid or expired token |
Token JWT sudah expired | Login ulang atau panggil /ums/refresh |
SSL certificate problem |
Self-signed cert di staging | Set UMS_VERIFY_SSL=false di .env |
Missing authorization token |
Request tanpa header Authorization: Bearer ... |
Tambahkan header Bearer token |
Route /ums/login not found |
Package belum ter-register | Jalankan composer dump-autoload dan clear cache |
Redirect ke /login setelah callback |
Session hilang atau callback URL mismatch | Pastikan UMS_CALLBACK_URL match dengan yang terdaftar di UMS |
| 403 padahal user punya permission | Nama module/permission case-sensitive | Cek exact match dengan yang ada di UMS Admin |
| JWKS cache stale setelah key rotation | Cache belum expired | Panggil UMS::invalidateJwksCache() atau php artisan cache:clear |
Session Data
Setelah SSO login berhasil, data berikut tersimpan di Laravel session:
| Key | Type | Isi |
|---|---|---|
ums_token |
string | JWT access token |
ums_refresh_token |
string | Refresh token |
ums_user |
array | Decoded JWT claims |
Security Notes
- ✅ Token divalidasi secara lokal menggunakan RSA public key (RS256) dari JWKS endpoint
- ✅ JWKS key di-cache selama 1 jam (configurable) untuk minimalisir network calls
- ✅ Refresh token hanya disimpan di server-side session (tidak di-expose ke client)
- ⚠️
UMS_VERIFY_SSL=false— hanya untuk development. Di production selalutrue - ⚠️ Pastikan
APP_KEYLaravel sudah di-set (session encryption) - ⚠️ Gunakan HTTPS di production untuk protect token di callback URL
Sample Project
Lihat full working example di:
sample/laravel-ums-integration-sample/
Untuk menjalankan:
cd sample/laravel-ums-integration-sample composer install cp .env.example .env php artisan key:generate # Update .env dengan UMS config php artisan serve
Buka http://localhost:8000 → Login with SSO → lihat dashboard test.
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 2
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-07-01