padosoft/laravel-iam-directory

Modulo opzionale Directory di Laravel IAM: LDAP/Active Directory (LdapRecord) in v1; SCIM in v2.

Maintainers

Package info

github.com/padosoft/laravel-iam-directory

pkg:composer/padosoft/laravel-iam-directory

Transparency log

Statistics

Installs: 167

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

v1.1.0 2026-06-29 13:04 UTC

This package is auto-updated.

Last update: 2026-06-30 20:37:00 UTC


README

Laravel IAM

Laravel IAM — Directory

Bring your LDAP / Active Directory into Laravel IAM — safely.
Directory login, Just-In-Time provisioning and group → role mapping, hardened against the classic identity-sync pitfalls.

Tests Latest Version on Packagist Total Downloads PHP Version License

📖 Read the documentation →

Why this package

Most teams already have their users in LDAP or Active Directory. Wiring that into an app usually means one of two bad outcomes: a brittle hand-rolled bind that silently fails open, or a "sync everything" job that happily takes over local accounts, keeps stale privileges forever, and lets a single wrong row in a group-mapping table escalate a user to admin.

laravel-iam-directory is the optional Directory module of Laravel IAM. It authenticates against your directory, provisions users Just-In-Time on first login, and keeps their IAM roles in sync with their directory groups — while closing every one of those pitfalls by design:

  • Anti-takeover — an email already owned by a non-directory account is never auto-linked. You get a conflict, not a hijack.
  • No privilege persistence — drop a user from an LDAP group and the matching role is revoked on the next sync. Manually-granted roles are never touched.
  • Protected roles — list the roles (e.g. iam:super_admin) that can never be granted through group mapping, no matter what the directory says.
  • Fail-closed — bad credentials or a connector error resolve to denied, never to a half-provisioned user.

The real LDAP transport (LdapRecord) is optional and isolated: the core works against any DirectoryConnector, so you can test, map and provision without ext-ldap installed.

Features

  • DirectoryAuthenticator — the login entry point: authenticate → map groups → provision/sync, all fail-closed.
  • GroupMapper — maps a user's directory groups to IAM roles (full_key), accepting both the full DN and the short CN, case-insensitively. Unmapped groups are ignored (default-deny — no implicit roles).
  • DirectoryProvisioner — JIT on first login (creates user + membership + grants) and an authoritative, idempotent sync afterwards that adds missing roles and revokes stale directory-sourced ones.
  • DirectoryJitPolicy — secure-by-default: require_verified_email, allowed_domains, approval_required, default_roles, group_mapping, and protected_roles.
  • DirectoryUser / DirectoryConnector — a normalized identity and a transport seam, so the core is decoupled from LDAP and trivially testable.
  • DirectoryOutcome — a typed result: provisioned · linked · conflict · pending · denied.
  • Optional Ldap\LdapConnector — the real LDAP/AD transport via LdapRecord, isolated so the package installs and analyses cleanly without ext-ldap.

Use cases

  • SSO-less LDAP/AD login. Let enterprise users sign in with their directory credentials, without standing up a full SAML/OIDC IdP.
  • Auto-provision on first login. New hire appears in AD → on first login they get an IAM user, membership and the right roles, with no manual onboarding.
  • Keep roles in sync with AD groups — safely. Group membership changes flow into IAM roles on every login, with stale roles revoked and protected roles never auto-granted.

Installation

composer require padosoft/laravel-iam-directory

Requirements: PHP 8.3+, Laravel 13, and a running Laravel IAM server (padosoft/laravel-iam-server).

The real LDAP/Active Directory transport is optional and needs the PHP ext-ldap extension:

composer require directorytree/ldaprecord-laravel   # enables Ldap\LdapConnector

Without it, the core (group mapping + JIT) still works against any DirectoryConnector you provide — handy for tests and non-LDAP sources.

Publish the config:

php artisan vendor:publish --tag=iam-directory-config

Quick start

1. Configure the directory & mapping

config/iam-directory.php:

return [
    // Target organization for provisioning (null = global users without a membership).
    'organization_id' => env('IAM_DIRECTORY_ORG'),

    'jit' => [
        'require_verified_email' => true,
        'allowed_domains'        => ['acme.com'],          // [] = no domain restriction
        'approval_required'      => false,
        'default_roles'          => ['iam:tenant_member'], // bootstrap roles (full_key)
        'group_mapping'          => true,
        // Roles that can NEVER be granted via group mapping (manual-only):
        'protected_roles'        => ['iam:super_admin'],
    ],

    // Directory group → IAM role(s). Key = full DN or short CN (case-insensitive).
    'group_map' => [
        'cn=warehouse-admins,ou=groups,dc=acme,dc=com' => 'warehouse:admin',
        'developers' => ['app:developer', 'app:deployer'],
    ],
];

2. Authenticate a user against the directory

use Padosoft\Iam\Directory\DirectoryAuthenticator;

$outcome = app(DirectoryAuthenticator::class)->login($username, $password);

match ($outcome->status) {
    'provisioned', 'linked' => Auth::loginUsingId($outcome->userId), // roles already synced
    'pending'  => back()->withErrors(__("Access pending: {$outcome->reason}")),
    'conflict' => back()->withErrors(__('That email belongs to an existing account — manual link required.')),
    'denied'   => back()->withErrors(__('Invalid credentials.')),
};

3. The roles stay in sync — automatically

On every login the provisioner re-syncs the user's directory-sourced roles:

  • user added to developers in AD → gets app:developer, app:deployer
  • user removed from warehouse-adminswarehouse:admin is revoked (manual grants untouched)
  • iam:super_admin mapped by a rogue group row → ignored (it's a protected role)

4. Use a custom (non-LDAP) source

Implement DirectoryConnector to provision from any source — no ext-ldap needed:

use Padosoft\Iam\Directory\Contracts\DirectoryConnector;
use Padosoft\Iam\Directory\DirectoryUser;

final class CsvDirectoryConnector implements DirectoryConnector
{
    public function authenticate(string $username, string $password): ?DirectoryUser
    {
        // verify credentials; return null (= denied) on failure — never throw an opaque error
        return new DirectoryUser(
            username: $username,
            email: 'jane@acme.com',
            emailVerified: true,
            groups: ['developers'],
        );
    }

    public function find(string $username): ?DirectoryUser { /* lookup without credentials */ }
}

⚠️ Security model. This module is deliberately strict: it refuses account takeover by email collision, revokes stale directory roles on sync, and never grants protected_roles from the directory. Don't relax these in a custom connector — see docs/provisioning-and-security.md.

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 Client for apps consuming Laravel IAM: middleware, Gate adapter
laravel-iam-ai Optional AI module: advisory-only governance
laravel-iam-directory (this repo) Optional directory module: LDAP / Active Directory (LdapRecord); SCIM in v2
laravel-iam-bridge-spatie-permission Migration bridge from spatie/laravel-permission

Documentation

📖 Full documentation: doc.laravel-iam-directory.padosoft.com — a 22-page docmd site with semantic search, covering:

The Markdown sources live in docs-site/docs/; the site is built with docmd and deployed to Cloudflare Pages.

Security

Laravel IAM is fail-closed by design. This module adds directory-specific hardening: anti-takeover (email collision → conflict), stale-privilege revocation on sync, and protected_roles that the directory can never grant. The directory assigns roles; the PDP, not this module, decides allow/deny. If you discover a security issue, email security@padosoft.com rather than opening a public issue.

License

MIT © Padosoft. See LICENSE.