FabrikFabrik
FabrikAdministration

LDAP

LDAP configuration visibility, connection testing, directory browsing, and how LDAP groups map to Django flags like is_staff and is_superuser.

LDAP lets Fabrik delegate authentication to your corporate directory. Users log in with their LDAP credentials; accounts mirror into Django on first login; group memberships in LDAP can grant Django flags like is_staff or is_superuser. The admin page is read-only — you configure LDAP through environment variables, and the UI reflects what the server has loaded.

Enabling LDAP

LDAP is off by default. To enable:

  1. Set LDAP_ENABLED=true in the server .env.
  2. Configure connection details: LDAP_SERVER_URI, LDAP_BIND_DN, LDAP_BIND_PASSWORD, LDAP_USER_DN, LDAP_GROUP_DN.
  3. Restart the backend.

Until LDAP_ENABLED is true, all LDAP endpoints return an informational response telling you it's disabled. No partial LDAP state — either the backend has it fully wired up at startup, or it doesn't.

LDAP settings are intentionally not editable from the UI. They live in environment variables because they're deployment-level infrastructure; changing them requires a backend restart, and UI-editable settings for this would mislead admins into thinking a change took effect when it hadn't.

The status page

Settings → Administration → LDAP shows everything the running backend has loaded, with no secrets included.

Server section

  • URI — the LDAP server URL (ldap://, ldaps://, or ldapi:/// for local sockets).
  • Bind DN — the service account DN used to search the directory.
  • User search base — the subtree where user entries live (e.g. ou=users,dc=example,dc=com).
  • Group search base — the subtree where groups live.

The bind password is never surfaced to the UI — it's loaded from the environment at startup and stays server-side only.

Group mappings

Fabrik's LDAP integration uses Django's AUTH_LDAP_USER_FLAGS_BY_GROUP setting to promote users based on group membership. The mapping table on this page lists every flag-to-group mapping:

Django flagMeaningTypical LDAP group
is_activeUser can log inDefault: every user gets this
is_staffDjango admin accesse.g. cn=fabrik-staff,ou=groups,…
is_superuserFull admin (bypass all permissions)e.g. cn=fabrik-admins,ou=groups,…

When a user logs in via LDAP and is a member of the mapped group, the flag gets set on their Django user automatically. When they're removed from the group, the flag is cleared on their next login — if AUTH_LDAP_ALWAYS_UPDATE_USER is true (it usually is).

Mapping is_superuser to an LDAP group means anyone who joins that group in the directory becomes a Fabrik superuser. Make sure the directory group is tightly controlled. A common mistake: mapping to a broad group like cn=it-staff and accidentally giving every IT person full admin rights.

Attribute map

The AUTH_LDAP_USER_ATTR_MAP dict controls which LDAP attributes populate which Django user fields on login:

Django fieldTypical LDAP attribute
first_namegivenName
last_namesn
emailmail

Other attributes the directory browser surfaces (title, department, employee ID, phone, office) are informational — they show in the LDAP user listing but aren't stored on the Django side.

Mirror groups

AUTH_LDAP_MIRROR_GROUPS controls whether LDAP group memberships also create matching Django groups:

  • On — every LDAP group the user belongs to shows up as a Django group of the same name. Permissions granted to that Django group apply automatically. Useful for large, directory-driven deployments.
  • Off — LDAP groups don't create Django groups; only AUTH_LDAP_USER_FLAGS_BY_GROUP takes effect. You assign Fabrik groups manually after LDAP login. Useful when you want Fabrik's group model to stay independent of the directory.

AUTH_LDAP_ALWAYS_UPDATE_USER complements this — when true, each login re-syncs attributes and group memberships. When false, data syncs only on first login and then goes stale.

Testing the connection

The Test connection button runs a full check:

  1. Opens an LDAP connection with a 5-second network and operation timeout.
  2. Binds with LDAP_BIND_DN / LDAP_BIND_PASSWORD.
  3. Searches for users matching (uid=*) under the user DN — returns a count.
  4. Searches for groups of class groupOfNames under the group DN — returns a count.
  5. Unbinds cleanly.

If all steps succeed, the UI shows a green banner with the server URI and the user/group counts. If any step fails, the error message from the LDAP library surfaces in a red banner — typically one of: connection refused (server down or URI wrong), invalid credentials (bind DN or password wrong), no such object (search base wrong), timeout (network path blocked).

Run Test connection after any environment change and after backend restarts to confirm things are actually working.

Browsing the directory

Two read-only listings help debug sync issues.

LDAP users

Lists everyone in the user search base with the attributes Fabrik cares about (uid, cn, givenName, sn, mail, title, departmentNumber, employeeNumber, telephoneNumber, physicalDeliveryOfficeName) plus:

  • LDAP groups — every groupOfNames the user is a member of.
  • Synced to Django — whether a Django user exists with matching uid as username.
  • Django last login — timestamp if the user has ever logged in.

The "synced" column is the most useful signal when troubleshooting. A user who exists in LDAP but hasn't logged into Fabrik won't have a Django row yet — that's normal, they mirror on first successful login. A user who used to log in but now can't: check if their LDAP attributes changed (wrong email, deactivated), check group membership.

LDAP groups

Lists every groupOfNames with member count, member uids, and whether the group is mapped to a Django flag. This is the fast way to audit "who has is_superuser" — look at the group mapped to is_superuser and read the member list.

How login actually works

When LDAP_ENABLED=true, the authentication flow:

  1. User enters username and password on the login form.
  2. Django authentication middleware first tries the LDAP backend.
  3. LDAP backend searches for the user DN under LDAP_USER_DN matching the username.
  4. Attempts to bind with that DN and the provided password.
  5. On success, syncs attributes per AUTH_LDAP_USER_ATTR_MAP and updates Django flags per AUTH_LDAP_USER_FLAGS_BY_GROUP.
  6. Creates the Django user if it doesn't exist.
  7. Issues JWT tokens as usual.

If LDAP bind fails, Django falls through to the local authentication backend — so local accounts (the bootstrap admin, service accounts) still work alongside LDAP. This hybrid is deliberate: it means you can enable LDAP without breaking the accounts you used to set up the deployment.

Group assignment strategies

Three common patterns:

  • LDAP-driven (mirror on). LDAP groups mirror to Django groups. Permissions get assigned to the Django groups that correspond to LDAP groups. Users land in the right Fabrik groups automatically — but anyone with access to the directory can change who sees what, so the directory becomes security-critical.

  • Hybrid (mirror off, flags on). LDAP grants is_staff or is_superuser via flag mapping, but Fabrik group memberships are managed manually inside Fabrik. Nice middle ground: LDAP is the "who can log in" and "who's an admin" layer, Fabrik is the "what can they do" layer.

  • Local groups only (mirror off, flags off). LDAP is just an authentication source. Every user is assigned to Fabrik groups by an admin after first login. Most control, most manual work. Best for small teams.

When LDAP is off

If LDAP is off, every endpoint on this page returns a short response telling you it's off — no silent failures. The rest of Fabrik still works exactly as it would without LDAP: local users, local passwords, local groups.

Troubleshooting

LDAP issues that come up often:

  • "Test connection fails with 'invalid credentials'." The bind DN or password in the environment is wrong. The password isn't surfaced in the UI, so you can't verify it from here — check the .env file directly and restart the backend after changes.
  • "User can log in but isn't an admin." Check the AUTH_LDAP_USER_FLAGS_BY_GROUP mapping on the status page, and check the LDAP group listing to confirm the user is actually a member of the mapped group.
  • "User's email or name is stale." AUTH_LDAP_ALWAYS_UPDATE_USER is probably off. Either enable it, or have the user log out and back in after the directory change.
  • "An LDAP user is deactivated in the directory but can still log in." LDAP deactivation doesn't propagate automatically. AUTH_LDAP_USER_FLAGS_BY_GROUP clears flags on next login, but if no login happens the Django user stays active. Deactivate in Fabrik manually or wait for next login to re-sync.
  • "I can't find a user in the LDAP listing." They're outside the LDAP_USER_DN subtree. Widen the search base or re-org the directory.
  • "Mirror groups created dozens of random Django groups." Turn off AUTH_LDAP_MIRROR_GROUPS if you want to keep Fabrik group membership under manual control. The mirrored groups stay in the database after toggling off — clean them up manually if they're confusing the group list.

LDAP ties identity to your directory. The audit trail — next page — ties every action in Fabrik to the identity that did it.