Architecture overview
Architecture overview
The shape of the package
laravel-iam-client is deliberately small and layered. Each layer has one job and depends only on the layer
below it.
flowchart TD
subgraph surface["App surface"]
MW["iam.can / iam.auth middleware"]
GA["IamGateAdapter (Gate::before)"]
FA["Iam facade"]
end
subgraph core["Application API"]
IC["IamClient<br/>can / denies / check / request"]
end
subgraph dto["Contract DTOs"]
DR["DecisionRequest"]
ID["IamDecision"]
end
subgraph transport["Transport (Decider)"]
CD["CachingDecider"]
LD["LocalDecider"]
HD["HttpDecider"]
end
MW --> IC
GA --> IC
FA --> IC
IC --> DR
IC --> CD
CD --> LD
CD --> HD
LD --> ENG["AuthorizationEngine (in-process PDP)"]
HD --> API["Admin API · POST /decisions/check"]
LD --> ID
HD --> ID
Components
| Component | Namespace | Role |
|---|---|---|
IamClient |
Padosoft\Iam\Client |
Application API; builds a DecisionRequest, returns an IamDecision. |
Iam facade |
…\Client\Facades |
Static sugar over IamClient. |
DecisionRequest |
…\Client |
Immutable query DTO; toArray() + cacheKey(). |
IamDecision |
…\Client |
Immutable outcome DTO; granted(), fromArray(), toArray(). |
Decider |
…\Client\Contracts |
Transport interface: decide(DecisionRequest): IamDecision. |
LocalDecider / HttpDecider / CachingDecider |
…\Client\Deciders |
In-process / remote / caching transports. |
IamGateAdapter |
…\Client\Gate |
Registers Gate::before. |
IamAuthenticate / IamCan |
…\Client\Http\Middleware |
The iam.auth / iam.can aliases. |
IamClientServiceProvider |
…\Client |
Wires it all together. |
Wiring (the service provider)
IamClientServiceProvider extends spatie/laravel-package-tools’ PackageServiceProvider.
packageRegistered()binds three singletons:Decider(mode + cache),IamClient(config),
IamGateAdapter(intercept).packageBooted()aliases the middleware if free, and registers the Gate adapter when
gate.enabled.
flowchart LR
CFG["config/iam-client.php"] --> SP["IamClientServiceProvider"]
SP -->|mode + cache| DEC["Decider singleton"]
SP -->|config| CL["IamClient singleton"]
SP -->|intercept| AD["IamGateAdapter singleton"]
SP -->|if free| ALIAS["iam.can / iam.auth aliases"]
SP -->|if gate.enabled| GREG["Gate::before registered"]
Design properties
- Transport-agnostic application code. The surface (middleware, Gate, facade) depends on
IamClient,
which depends on theDeciderinterface — never a concrete transport. Swappinglocal↔httpis a
config change. See Transports. - Immutable DTOs.
DecisionRequestandIamDecisionarefinal readonly, so a decision can’t be
mutated after the PDP returns it, and a request can’t drift between building and sending. - Fail-closed at every layer. Subject resolution, transport, and response parsing each default to deny.
See Fail-closed authorization. - Non-invasive coexistence. The Gate adapter intercepts only namespaced abilities by default, and the
middleware aliases yield to any already-registerediam.can. The client slots into an existing app
without displacing its authorization.
Dependencies
The package depends on padosoft/laravel-iam-contracts
for the AuthorizationEngine interface (the local seam) and shared DTO conventions, and on
guzzlehttp/guzzle for the http transport. It does not depend on the server package at runtime in
http mode — only the contract.
See also
- Decision pipeline — a single request, end to end.
- Transports (the Decider seam)
- Architecture decisions (ADR)