padosoft / laravel-iam-client
Client Laravel per app che consumano Laravel IAM: login OIDC, verifica JWT/JWKS, introspection, middleware iam.auth/iam.can, Gate adapter, policy cache, webhook receiver.
Requires
- php: ^8.3
- guzzlehttp/guzzle: ^7.0
- lcobucci/jwt: ^5.0
- padosoft/laravel-iam-contracts: ^1.0
- spatie/laravel-package-tools: ^1.16
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.18
- orchestra/testbench: ^10.0|^11.0
- padosoft/laravel-iam-server: ^1.0
- pestphp/pest: ^3.0
- pestphp/pest-plugin-laravel: ^3.0
This package is auto-updated.
Last update: 2026-06-29 11:52:12 UTC
README
Laravel IAM — Client
The drop-in Laravel client for apps that delegate authorization to a Laravel IAM server.
iam.can / iam.auth middleware, a Gate adapter so $user->can() just works, decision caching — fail-closed by design.
Why this package
Laravel IAM centralizes who can do what into one Policy Decision Point
(PDP): RBAC + ABAC + ReBAC, step-up assurance, tenant isolation, tamper-evident audit. But your consuming
apps shouldn't have to learn any of that — they already speak Laravel: middleware, Gate, policies,
$user->can().
laravel-iam-client is the bridge. You point it at your IAM server, and authorization decisions flow
through the tools your app already uses:
Route::put('/invoices/{invoice}', [InvoiceController::class, 'update']) ->middleware('iam.can:billing:invoices.update,invoice'); // ← decided by the central PDP
It works in two modes with identical app code: local (the IAM server lives in the same app — the
client calls the PDP in-process, zero network) or http (remote server — the client calls the Admin API).
Swap a single env var to move from a modular monolith to a distributed deployment.
Fail-closed, always. Every transport (
LocalDecider,HttpDecider) denies on any error — unreachable PDP, non-2xx, unparseable body, engine exception. There is no fail-open opt-out: an outage never opens the doors.
Features
iam.can:<permission>middleware — a drop-in replacement for Spatie'spermission:middleware, decided by the central PDP. Bind a route parameter (iam.can:billing:invoices.update,invoice) and the decision is scoped to that resource — including through route-model binding.iam.authmiddleware — fail-closed guard that 401s any request without a resolvable subject.- Gate adapter — registers
Gate::beforeso$user->can('billing:invoices.update'),@canBlade directives andauthorize()in controllers all consult IAM. By default it only intercepts namespaced abilities (those containing:), leaving your local Gates/policies untouched. - Pluggable transports (
Decider) —LocalDecider(in-process PDP),HttpDecider(remote Admin API), both fully fail-closed. Your app code never knows which is in use. CachingDecider— decisions are deterministic per input, so they're cached for a short TTL;explainqueries are never cached.- Step-up aware — a permit that requires a higher assurance level (
requiresStepUp) is treated as not yet granted by middleware and the Gate adapter (IamDecision::granted()), so you can't accidentally let a low-AAL session through. Iamfacade —Iam::can($user, 'warehouse:stock.adjust', ['amount' => 300])for ABAC checks with context.
Use cases
- Protect routes against a central policy. Replace scattered role checks with
->middleware('iam.can:hr:salaries.view')— the rule lives in IAM, not in your app. - Keep using Laravel's authorization API.
@can,$user->can(), policies andauthorize()keep working; the answer just comes from the central PDP. - Per-resource (ReBAC) checks.
iam.can:projects:edit,projectbinds the decision to the bound{project}— "can this user edit this project", not the permission in the abstract. - Go from monolith to services without rewriting. Start with
mode=local(same app), flip tomode=httpwhen you extract the IAM server — the controllers don't change.
Installation
composer require padosoft/laravel-iam-client
Publish the config:
php artisan vendor:publish --tag=laravel-iam-client-config
Requirements: PHP 8.3+, Laravel 11/12+. Depends on
padosoft/laravel-iam-contracts.
Quick start
1. Configure the transport
config/iam-client.php (publish it, then set env):
# Remote IAM server (use mode=local if the server lives in this same app) IAM_CLIENT_MODE=http IAM_CLIENT_BASE_URL=https://iam.example.com/api/iam/v1 IAM_CLIENT_TOKEN=your-service-bearer-token # Defaults applied to every decision query IAM_CLIENT_APP=billing IAM_CLIENT_ORG=org_acme
That's it — the service provider wires the right decider (with caching) and registers the middleware aliases and the Gate adapter automatically.
2. Protect routes with iam.can
use Illuminate\Support\Facades\Route; // Permission only Route::get('/reports', [ReportController::class, 'index']) ->middleware(['auth', 'iam.can:reports:view']); // Permission bound to a route resource (ReBAC): "can edit THIS invoice" Route::put('/invoices/{invoice}', [InvoiceController::class, 'update']) ->middleware(['auth', 'iam.can:billing:invoices.update,invoice']);
iam.auth ensures there's a resolvable subject (401 otherwise); iam.can denies with 403 when IAM says
no — or when a step-up is required but not yet satisfied.
3. Use the Gate adapter (your existing code keeps working)
// In a controller public function update(Request $request, Invoice $invoice) { $this->authorize('billing:invoices.update', $invoice); // → consults the central PDP // ... }
@can('reports:view') <a href="/reports">Reports</a> @endcan
4. Ask IAM directly with the facade
use Padosoft\Iam\Client\Facades\Iam; // ABAC: pass context facts; IAM evaluates the policy if (Iam::can($user, 'warehouse:stock.adjust', ['amount' => 300, 'resource' => 'wh_milan'])) { // approved } // Need the full decision (step-up, explanation)? $decision = Iam::check($user, 'billing:invoices.delete', ['explain' => true]); $decision->granted(); // permit AND no pending step-up $decision->requiresStepUp; // true → ask the user to re-authenticate at a higher AAL $decision->explanation; // why (when explain=true)
How it fits the ecosystem
| Package | Role |
|---|---|
| laravel-iam-contracts | Shared interfaces & DTOs — the dependency root |
| laravel-iam-server | The IAM server: identity, PDP, OAuth/OIDC, audit, governance, Admin API & panel |
| laravel-iam-client (this repo) | Consumer SDK: iam.can/iam.auth middleware, Gate adapter, decision caching |
| laravel-iam-ai | Optional AI module: advisory-only governance (redaction + hallucination guard + audit) |
| laravel-iam-directory | Optional directory module: LDAP / Active Directory (LdapRecord); SCIM in v2 |
| laravel-iam-bridge-spatie-permission | Migration bridge from spatie/laravel-permission: scan, shadow mode, decision diffing, cutover |
Documentation
A docmd doc-site lives in docs/: start at docs/index.md, then
Getting started, Concepts,
Middleware & Gate, Configuration and the
Reference.
Security
This client is fail-closed by design: any transport error, unreachable PDP, non-2xx response or engine exception resolves to deny — never an allow, never an opaque 500. Step-up-required permits are treated as not-yet-granted. There is no fail-open switch. If you discover a security issue, please email security@padosoft.com rather than opening a public issue.
