Skip to content

LDAP and Active Directory

Ed Mozley edited this page Aug 16, 2026 · 7 revisions

LDAP & Active Directory

FreeITSM can check people's passwords against your existing directory β€” Active Directory, OpenLDAP, FreeIPA, 389 Directory Server, or anything else that speaks LDAP. Staff sign in with the username and password they already use everywhere else, and you stop hand-creating an account for every new starter.

Requested in issue #47.

This is not single sign-on. With SSO the browser bounces to your identity provider and back, and you get one shared session across applications. With LDAP, people type their directory password into FreeITSM's own login form and we check it with the directory. Both are configured in the same place β€” System β†’ Authentication β€” because both answer "how do people sign in", but they behave differently. If your directory is already fronted by Entra ID or Keycloak, prefer SSO.


Why you'd want it

Without a directory With a directory
You create a FreeITSM account for every new starter They appear automatically on first sign-in
Another password for people to forget The password they already use
A leaver keeps working until someone remembers to disable them here Disabled in the directory = locked out here, immediately
Access is whatever you set by hand Access follows your existing groups

How it works

Someone types r.patel, not their full directory path, so FreeITSM does this on every sign-in:

  1. Connect to your directory and bind as a read-only service account, so it's allowed to look people up.
  2. Search for the person to find their full DN (their entry's path in the tree).
  3. Bind as that person with the password they typed. If the directory accepts the bind, the password was right.
  4. Read their groups to decide what β€” if anything β€” they're allowed to be.

That's why the setup form asks for a server, a service account and a base DN: they're the ingredients for those steps.

FreeITSM never reads, compares or stores anyone's directory password. It never needs write access to your directory.


Setting one up

System β†’ Authentication β†’ + Add, then set Type to LDAP / Active Directory.

Pick the Active Directory or OpenLDAP preset first β€” it fills in the search filter and attribute names for that flavour of directory, leaving you four things to supply:

  • Server β€” a domain controller's hostname or IP, e.g. dc1.example.local.
  • Service account β€” a read-only lookup account. AD accepts svc-freeitsm@example.local; OpenLDAP wants a full DN like cn=svc-freeitsm,dc=example,dc=com.
  • Base DN β€” where to search from, e.g. DC=example,DC=local, or narrow it to OU=Staff,DC=example,DC=local.
  • Encryption β€” see below.

Then use Test before saving. Leave the test user blank to check the service account can connect and read; fill one in to run a real sign-in and see exactly which name, email and groups come back. It's the fastest way to catch a wrong attribute name or a too-narrow base DN.

Encryption

⚠️ With encryption set to None, people's passwords cross your network in the clear on every sign-in. Use STARTTLS or LDAPS in production.

Many Active Directory servers refuse password binds over unencrypted LDAP anyway. If plain LDAP fails with an error mentioning strong authentication, that's why β€” switch to LDAPS.

Requirements

The PHP ldap extension must be enabled (extension=ldap in php.ini, then restart the web server). Some setups have two php.ini files β€” one for the web server, one for the CLI β€” and both need it. The Authentication page tells you plainly if it's missing.


Controlling access by group

Auto-create is the point of connecting a directory β€” but on its own it means everyone your directory recognises becomes an analyst. Point that at a 500-person company and you get 500 analysts. Groups are what stop that.

Setting Effect
Analyst group Members get an analyst account and can use the main FreeITSM login
Self-service user group Members get a self-service account β€” they can raise and track their own tickets, but cannot sign in as an analyst
In neither group Cannot sign in at all, even with a correct password
Both blank No gate β€” anyone the directory recognises becomes an analyst. Fine for a small single-team install; risky anywhere else

Type either the group's plain name (ITSM-Analysts) or its full DN. Case doesn't matter.

Nested groups work on Active Directory. If your analyst group contains other groups rather than people directly, members of those inner groups still get in β€” the AD preset uses AD's chain-matching rule to walk the nesting. OpenLDAP has no equivalent, so there you must name a group that directly contains the people.

It fails safely. If FreeITSM can't read your groups, nobody is let in by accident: an unreadable group list denies access rather than granting it. So if everyone is suddenly refused, suspect the group settings rather than people's passwords.


What happens on first sign-in

With Auto-create users on first login (JIT) on, someone in the analyst group who has never signed in before gets an analyst account built from their directory entry:

  • Username from their directory username (deduplicated if it's taken).
  • Full name and email from their directory entry.
  • No usable local password β€” they sign in via the directory, so a random unusable hash is stored.
  • Module access from Default module access for auto-created users. Leave it blank and they get every module; set it (e.g. tickets, knowledge) so auto-created people aren't handed the keys.

πŸ”΄ Fixed in #904 β€” before that, this setting did nothing. The module grants were written correctly, but analysts.can_access_all_modules defaults to 1 and neither provisioning path cleared it, so the permission lookup short-circuited on that flag and never read the grants. Every analyst auto-created by a directory or by an identity provider had full module access regardless of what you'd configured. Existing accounts are not retroactively restricted β€” if you relied on this, check System β†’ Analysts and set their access by hand.

The account is linked to the person's immutable directory ID (objectGUID on AD, entryUUID on OpenLDAP), not their name or DN β€” so renaming them, or moving them between OUs, doesn't orphan or duplicate their account.

⚠️ Unless you leave the ID attribute blank. If no immutable-id attribute is configured, FreeITSM falls back to the DN as the link (includes/ldap.php:346) β€” and then a rename or an OU move does orphan the account, creating a second one on their next sign-in. Keep objectGUID / entryUUID set; the presets do.

Staff with no email address

Plenty of businesses never give warehouse, shop-floor or shift staff a mailbox. They can use FreeITSM anyway β€” as analysts (#872) and, since #902, in the self-service portal.

They sign in with the username their directory knows them by rather than an address. Everything else follows:

What happens
Signing in The portal's first box takes an email or a username as soon as any directory is configured β€” nothing extra to set. (Without a directory it stays an email-only field, since a bare username would mean nothing.)
Their company Comes from the directory they signed in against. With no address there is no domain to derive it from, so a company-owned directory vouches for its people
Raising tickets Works normally. The message simply carries no sender address
How analysts see them By name β€” and they are searchable by their sign-in name
Replying to them FreeITSM says there is no mailbox and suggests sharing a note, which they read in the portal
Notifications Not sent β€” there is nowhere to send them. They check the portal

⚠️ On a MULTI-COMPANY install, someone signing in with a bare username can only be matched against a directory shared by all companies β€” a username on its own doesn't say which company they belong to. A company-owned directory needs its people to have addresses. Single-company installs are unaffected.

There is no need to invent a fake address like w.noemail@example.local. That was considered and rejected: a fake address is indistinguishable from a real one, so an analyst would reply to it and the reply would bounce into nowhere. FreeITSM records that there is no address, and says so.


Importing people, rather than waiting for them to sign in

Everything above creates somebody the first time they log in. If you want your staff in FreeITSM before that - to assign them equipment, raise tickets for them, or record who reports to whom - you can import them from the same directory.

That matters because the people who hold equipment are largely the people who never log in at all.

πŸ“₯ Importing people from Active Directory or LDAP - browsing your directory and ticking what to import, checking the field mapping, previewing before you commit, and what happens when somebody leaves.

It shares this page's connection settings, so there is nothing new to configure to get started.


How FreeITSM decides: local, directory, or single sign-on

There is one box on each login page. What you type β€” an email address, or a username β€” plus what FreeITSM already knows about that account decides what happens next. Nobody has to pick "I am an LDAP user" from a dropdown.

The decision happens in two moments: once when you leave the first box (does this person need redirecting to an identity provider?), and once when you press Sign in (how do we check this password?).

flowchart TD
    A["You type an email address<br/>or a username"] --> B{"Single sign-on<br/>switched on?"}
    B -- No --> LOCALFORM["Show the password box"]
    B -- Yes --> C{"Is this account pinned<br/>to an OIDC provider?"}

    C -- Yes --> SSO["Redirect to that provider<br/>(Entra ID, Keycloak, Okta…)"]
    C -- No --> D{"PORTAL only:<br/>multi-company, and does their<br/>email domain map to a company<br/>that owns identity providers?"}

    D -- "exactly 1" --> SSO
    D -- "2 or more" --> CHOOSE["Ask which one"]
    D -- "none, or analyst login" --> LOCALFORM

    LOCALFORM --> E["You type a password<br/>and press Sign in"]
    E --> F{"Do we already know<br/>this account?"}

    F -- "Yes, pinned to a DIRECTORY" --> BIND["Check it by binding<br/>to that directory"]
    F -- "Yes, pinned to OIDC" --> REFUSE["Refuse β€” there is no<br/>password to check.<br/>Use the SSO button"]
    F -- "Yes, ordinary account" --> LOCAL["Check the stored<br/>password hash"]
    F -- "No such account" --> TRY["Try the directories<br/>(this is how new starters<br/>get an account)"]

    TRY --> BIND
    BIND --> GATE{"Which group are<br/>they in?"}
    GATE -- "analyst group" --> INA["Analyst access"]
    GATE -- "user group" --> INP["Self-service portal"]
    GATE -- "neither" --> DENY["Refused, even though<br/>the password was right"]

    LOCAL --> INA
    SSO --> INA
Loading

Company routing is portal-only. Analysts belong to your organisation, not to a client company, so resolve_login.php:70 runs that branch only for portal=self-service. An analyst is either pinned to a provider or sees the password box β€” they are never asked "which company are you with?".

Why the first question only ever routes to OIDC

A directory has nowhere to send you. OIDC works by redirecting your browser to the provider and getting you back again; LDAP works by FreeITSM opening a connection and offering your password to the directory itself. So a directory user just types their password into the ordinary box β€” there is no button, and nothing to redirect to.

That is deliberate, and it is why an account pinned to a directory falls through to the password box while an account pinned to an identity provider is refused there.

Why an unknown account is offered to the directories

This is just-in-time provisioning, and it is the point of the feature: nobody wants to hand-create an account for every new starter. Somebody who exists only in your directory types their normal work credentials, and their FreeITSM account is built from their directory entry on the spot.

The group gate is what makes that safe. Without it, auto-create means every employee in a 500-person company becomes an analyst.

What "we already know this account" means

Analyst login Portal login
Looked up by analysts.username users.email or users.username
Pinned via analysts.auth_provider_id users.auth_provider_id
Directory identity stored in analyst_sso_identities user_sso_identities

The portal accepts a username as well as an address because a requester may have no mailbox at all β€” see Staff with no email address.

Which directories get tried

Only relevant when the account is unknown and we're about to offer the password around.

Login Directories tried
Analyst Every enabled global directory (tenant_id IS NULL)
Portal, single-company install Every enabled directory
Portal, multi-company, domain recognised That company's directories + global ones
Portal, multi-company, bare username or unknown domain Global only

⚠️ The portal list is scoped for a reason. On a multi-company install each client can own a directory, so trying them all would offer a password typed for one employer to every other client's domain controller β€” and increment the account-lockout counter on each, letting one person's typo lock accounts at a different company.

⚠️ One asymmetry between the two logins

The two pages don't behave identically when single sign-on is on and a directory is configured:

First box accepts A directory user with a bare username
Portal (self-service/login.php) Email or username (type="text" once a directory exists) Types it straight in
Analyst (login.php) Email only (type="email", line 751) Must first click "Sign in with a username and password"

Nothing is broken β€” the analyst link is deliberately relabelled from "Sign in with a local account" to "Sign in with a username and password" whenever a directory exists (login.php:736-738), precisely because calling it "local" would be a lie to the people who have no local account. But it is one extra click that the portal doesn't ask for, and the two pages now word the same idea differently.

The break-glass

?local=1 on either login page β€” analyst (login.php:740) or portal (self-service/login.php:67) β€” always reveals the username/password form, even when local login is switched off. If your identity provider or directory is unreachable, that is the way back in.


Interaction with local accounts and SSO

  • Local accounts keep working. Existing username/password analysts are unaffected.
  • Local password expiry never applies to directory-backed accounts β€” there's no local password to expire.
  • MFA still works. Unlike SSO (where your IdP handles it), a directory-backed analyst can still have FreeITSM's TOTP on top. The directory checks the password, FreeITSM checks the second factor.
  • Once assigned to a provider, the local password door closes. An analyst assigned to a directory can only get in via that directory; assigned to an SSO provider, they must use the SSO button. Their old password hash stops being accepted.
  • Portal self-registration cannot claim a directory-backed account (api/self-service/register.php, self-service/verify-email.php). Directory-backed accounts have no local password by design, so they looked "unclaimed" to the existing check. Email confirmation meant no takeover was possible β€” but the real owner could innocently register and set a password that silently never works, because a directory-pinned account routes to its directory and never checks a local hash. Both the registration path and the confirmation-token redemption now refuse β€” the second independently, because a token minted before the account was linked to a directory must not plant a password on it afterwards.
  • Break-glass is unchanged β€” see SSO Β§ break-glass. Keep one local admin.

Troubleshooting

"No such object", but the user definitely exists. Nearly always the service account's permissions, not a missing user β€” most directories report a subtree they aren't allowed to read as though it doesn't exist. Check the base DN, then check the service account can read it. OpenLDAP denies reads by default until you grant them.

Everyone is refused, even with the right password. Check Access by group. If a group is named and nobody matches it, everyone is denied by design. Test shows the groups it found and the access it worked out.

One person is refused, everyone else is fine. Are they in the right group? An Active Directory admins group is not automatically your analyst group β€” name whichever group actually holds your service desk staff.

"The account is disabled." They're disabled in the directory. That's the directory refusing the bind, and it's working as intended.

Signs in, but the account has no name or email. The attribute names don't match your directory. Run Test with that person and compare what comes back.

Anything mentioning strong authentication. Your directory requires an encrypted connection. Switch Encryption to LDAPS or STARTTLS.


See also

FreeITSM

Getting Started

Modules

Multi-tenancy (planned)

Blue sky thinking

Bugs resolved

Links

Clone this wiki locally