gdbots/iam-bundle
Identity and access management bundle for Laravel/PHP apps, integrating the Gdbots IAM service for authentication, user accounts, roles, and permissions. Provides configurable guards, middleware, and service bindings to wire IAM into your application quickly.
Reality check first: this is a Symfony bundle. There is no IamServiceProvider, no facade, no config/iam.php, and no config/bundles.php auto-discovery that Laravel will honor. Installing it in a Laravel app gives you the underlying IAM library plus Symfony DI/security contracts — the bundle wrapper itself is largely inert unless a Symfony kernel boots it. Plan your integration around the library, not the bundle.
v5.1.0 note before you install: the release is flagged __POSSIBLE BREAKING CHANGES__. It moves the bundle onto auth0/auth0-php 9.x and now hard-codes a Guzzle client because PSR-17/PSR-18 auto-discovery is disabled for security reasons. If you are already on 5.0.x, treat this as a dependency upgrade, not a patch — read the SDK's 8→9 upgrade notes alongside it.
Minimal steps:
composer require gdbots/iam-bundle:^5.1
composer why-not gdbots/iam-bundle # check symfony/* AND auth0/auth0-php AND guzzle conflicts
composer show auth0/auth0-php guzzlehttp/guzzle guzzlehttp/psr7 # confirm resolved versions
Then read, in this order:
vendor/gdbots/iam-bundle/src/DependencyInjection/Configuration.php — the authoritative config tree (service ids, storage backend, Auth0 domain/client credentials, role/permission sources). This is your real documentation; the README is thin.vendor/gdbots/iam-bundle/src/DependencyInjection/IamExtension.php — how services are constructed, which constructor args matter, and where the Guzzle client is instantiated (v5.1.0).auth0/auth0-php 9.x SdkConfiguration / Auth0 factory — this is the object graph the bundle builds for you.vendor/gdbots/iam/src/ — the domain model (Iam, user/role/permission objects, the check method you'll actually call).Bind the core service manually in app/Providers/IamServiceProvider.php:
public function register(): void
{
$this->app->singleton(\Gdbots\Iam\Iam::class, function ($app) {
return new \Gdbots\Iam\Iam(/* config array translated from config/iam.php */);
});
}
First use case: read the authenticated principal's roles/permissions and gate a single route. Wrap the library check in a middleware or Gate::define, not in controllers — you want one choke point before you have ten.
Pattern A — library-only integration (recommended). Skip the bundle entirely. Use the IAM classes (and the Auth0 SDK underneath them) directly and map them onto Laravel's auth stack. You keep Laravel's Auth, Gate, middleware, and testing idioms, and you avoid fighting two containers.
Pattern B — don't use this bundle just to reach Auth0. v5.1.0 confirms the bundle's job is wiring auth0/auth0-php. In Laravel you already have first-class options: auth0/login for the guard/flow, or the SDK standalone. Only take on this bundle if you specifically want its IAM/role abstraction layered on top.
Custom user provider + guard. Implement Illuminate\Contracts\Auth\UserProvider that resolves an IAM user id to an Authenticatable adapter. Keep the adapter thin — an id, roles(), permissions() — and let Laravel own session/remember-token concerns.
class IamUserProvider implements UserProvider {
public function retrieveById($id) { return $this->iam->findUser($id)?->toLaravelUser(); }
// ...
}
Gate/policy mapping. Define gates once at boot from the IAM permission namespace, or use a single wildcard gate:
Gate::before(fn ($user, $ability) => $user?->iam()->isGranted($ability) ? true : null);
Return null (not false) when IAM has no opinion so policies still get a chance to run.
Middleware for route protection. Route::middleware('iam:posts.publish') with a middleware that throws AuthorizationException. Keeps authorization declarative and greppable, and makes it trivial to audit which routes are protected.
Stateless APIs. For SPA/mobile, don't lean on Symfony's firewall/session assumptions. Resolve the principal from a bearer token in a middleware, attach it via Auth::setUser(), and keep IAM checks per-request. Console commands and queued jobs have no session — pass the acting principal explicitly as a job property, don't call Auth::user() inside the handler.
Caching. IAM decisions are usually a DB/HTTP round-trip — and in v5.1.0 they go out over Guzzle to the Auth0 Management API, so they are network round-trips, not local ones. Cache the permission set per user per request ($request->attributes) and a short-TTL cache (60–300s) across requests. Invalidate on role change via a model event or an explicit IamCache::forget($userId). Auth0 Management API rate limits are the practical ceiling here.
Testing. Bind a fake Iam in tests that returns a scripted permission set. Do not hit a real backend in feature tests. Because v5.1.0 hard-codes its Guzzle client (see Gotchas), you cannot swap the HTTP client through the container — stub at the Iam boundary you control, or push a Guzzle MockHandler onto a HandlerStack if you must exercise the SDK path.
Upgrading 5.0 → 5.1. Do it in a branch, not a patch release:
composer require auth0/auth0-php:^9.0 gdbots/iam-bundle:^5.1 -W
composer why-not auth0/auth0-php 8 # what's still pinned to the old major
Run your authorization feature tests against the new major, and diff the resolved auth0/auth0-php version before and after — the SDK's 9.x changes are where the breakage will surface, not in this package's thin wrapper.
If you must use the bundle: isolate it. Run a small Symfony service that boots the kernel and exposes an HTTP endpoint (/check, /user/{id}), and have Laravel call it. Trying to boot a Symfony bundle's DI inside a Laravel service provider works only for trivially-constructed services and breaks the moment the bundle touches security.*, parameter bags, or compiler passes.
"gdbots/iam-bundle": "5.1.0"), upgrade deliberately, and keep a lockfile you can revert. If the 8.x→9.x SDK jump is more than you want right now, staying on 5.0.x is a legitimate, defensible choice.guzzlehttp/guzzle and guzzlehttp/psr7 must be installed and resolvable; you cannot substitute Symfony HttpClient or Laravel's HTTP client; and a Guzzle major/minor conflict in your app becomes a blocker. Check with composer why-not guzzlehttp/guzzle ^7 before upgrading.$this->app->bind(ClientInterface::class, ...) won't reach it. For tests, reach for Guzzle's MockHandler/HandlerStack; for production, don't plan on instrumenting the transport from Laravel. Add your own logging wrapper above the Iam service instead.http_discovery back on to get a nicer dependency graph reopens the supply-chain risk the maintainer deliberately closed off. Accept the hard-coded client.%env(IAM_DSN)% and %iam.storage% are not resolved by Laravel's config(). Translate every placeholder in your own config/iam.php and pass a plain array into the library. Same applies to Auth0's env-driven SdkConfiguration — build it explicitly in a service provider.SdkConfiguration builder and its option keys are the usual casualty of an 8→9 bump. Anything you copied from an older tutorial (domain, client_id, client_secret, audience, management_token, cache/TTL options) needs verifying against 9.x, not assumed.config/bundles.php is ignored. The bundle is never "registered"; anything the bundle does at boot (compiler passes, cache warmers, tagged services, event subscribers) simply won't happen. Don't assume bundle-provided services exist.symfony/* constraints can conflict with the versions Laravel locks. Run composer why-not before committing; a forced downgrade of symfony/http-foundation is a bad trade.IAM_BOOTSTRAP env-gated command). Document it, or the first deploy stalls.array/file caching.isGranted decision path (user, ability, result, auth0_request_id) early; you'll need it.extend()), swap the user provider, and wrap isGranted in your own AuthorizationService so callers never bind to the package's types directly. That single indirection is what makes it replaceable later — and it's what will save you at the next auth0/auth0-php major.How can I help you explore Laravel packages today?