# Manage Active Directory users and groups with LDAP

import { Steps, TabItem, Tabs } from "@astrojs/starlight/components"

LDAP (Beta) runs directory operations from a Mindflow flow.

- **What you can do:** Look up users and groups, update profiles, create accounts and groups, enable or disable login,
  unlock and set passwords, and manage group membership.
- **Directories:** Named **Active Directory** operations target Microsoft AD. Eight generic **LDAP** operations work
  with Active Directory, OpenLDAP, and other LDAP-compatible directories when you need raw search, modify, rename, or
  delete.
- **Active Directory vs LDAP:** Use **Active Directory** for identity lifecycle operations such as provisioning,
  updating, and offboarding (joiner, mover, and leaver workflows). Use **LDAP** for filters or attributes those named
  actions do not cover.
- **Authentication:** Every step uses an **LDAP Simple Authentication** credential. The directory host, protocol, and
  port always come from that credential’s **Address** URL—not from the step body.

:::note[Beta]
LDAP (Beta) may evolve between releases: operation names, fields, or behavior can change. Studio is the authority for
the fields available in your environment.
:::

## When to use

- Provision or offboard Active Directory users.
- Modify allow-listed user profile attributes.
- Enable, disable, or unlock accounts.
- Manage group membership (add, remove, list, or check).
- Query OpenLDAP or unsupported AD attributes with generic LDAP operations.

:::note[When not to use LDAP]
Prefer a native vendor integration when the directory is only reached through that vendor’s REST API. Prefer
[Run remote commands with SSH](../run-remote-commands-with-ssh/) when the job is a remote script, not a directory
protocol call.
:::

## Before you begin

You need:

- An LDAP or Active Directory server Mindflow can reach.
- Firewall or security-group access for Mindflow’s platform **static egress IP** (ask your Mindflow administrator).
- Permission to create a credential in the environment. See [Credentials](/getting-started/credentials/).
- A bind account with least privilege for the operations your flow runs (read/search for lookups; delegated create,
  update, disable, password, or group rights as needed). Prefer a dedicated service account—not Domain Admin.

**Ports and schemes:** `ldaps://` on **TCP 636**, or `ldap://` on **TCP 389** (optionally with StartTLS).

:::caution[Use encryption in production]
For production directories, use `ldaps://` or enable **Use StartTLS**. LDAP Simple Authentication over an unencrypted
connection can expose credentials and may be rejected by directories that require secure LDAP binds.

The connector still supports plain `ldap://` for non-password operations. That is **supported**, not **recommended** for
production.

**Set password** and **Create user** (when you send a password) **require** `ldaps://` or StartTLS.
:::

:::caution[Private directories]
If the directory is only on a private LAN and is not reachable from Mindflow’s static egress IP, LDAP steps cannot
connect until private connectivity is available. Contact Mindflow support for your environment’s reachability options.
:::

## Guide

### Enable and add LDAP

Activate **LDAP (Beta)** for the environment, then add its operations from a flow.

LDAP (Beta) groups its operations into **Active Directory** and **LDAP** sections.

<ol>
<li>

**Open Integrations.** In Studio, go to **Integrations**.

</li>
<li>

**Activate LDAP (Beta).** Open the **Mindflow** vendor group, search for `ldap`, then turn on **LDAP (Beta)** if it is
not already activated. Activating (favoriting) the service makes it available under **Add a step** →
**Integrations**. Services that already have an environment credential can also appear there.

<div class="docs-screenshot-compact">

![Integrations Mindflow page shows LDAP (Beta) activated after searching for ldap](./assets/enable-ldap.png)

</div>

</li>
<li>

**Add a step.** In a flow, open **Add a step**, choose **Integrations**, then **LDAP (Beta)**. Pick an
**Active Directory** action for named user and group work, or an **LDAP** operation for a raw directory call.

<div class="docs-screenshot-compact">

![Add a step menu for LDAP (Beta) listing LDAP operations with Active Directory section below](./assets/ldap-add-step-categories.png)

</div>

</li>
</ol>
### Create an LDAP credential

Every LDAP step authenticates with an **LDAP Simple Authentication** credential. The **Address** controls the server,
protocol, and port for every operation that uses that credential.

| Field                                | Role                                                                                     |
| ------------------------------------ | ---------------------------------------------------------------------------------------- |
| **Address**                          | Host, scheme, and port (`ldaps://dc01.example.com:636` or `ldap://dc01.example.com:389`) |
| **Bind DN**                          | Directory identity used to bind                                                          |
| **Bind Password**                    | Password for that bind identity                                                          |
| **Use StartTLS**                     | Upgrade a plain `ldap://` connection to TLS before bind                                  |
| **Reject Unauthorized Certificates** | When on, Mindflow validates the server certificate (recommended for production)          |

:::note[Least privilege]
The bind identity needs read/search rights for lookup operations, plus the delegated Active Directory permissions for
any create, update, disable, password, or group changes your flow performs. Use a dedicated service account with least
privilege.
:::

<ol>
<li>

**Open Environments.** Open the environment that will run the flow, then choose **Add credential**.

</li>
<li>

**Choose LDAP (Beta).** Set **Service** to **LDAP (Beta)**. Set **Credential name**, and keep **URL** as **Custom**.

</li>
<li>

**Fill Address and bind fields.** Prefer `ldaps://directory.example.com:636` for production. Fill **Bind DN** (for
example `CN=mindflow-svc,OU=Service Accounts,DC=example,DC=com`) and **Bind Password**.

<div class="docs-screenshot-compact">

![Create Credential for LDAP (Beta) with Corp AD bind, ldaps://dc01.example.com:636, Bind DN filled, Reject Unauthorized Certificates on](./assets/ldap-credential.png)

</div>

</li>
<li>

**Optional: set TLS options.** For `ldap://`, turn on **Use StartTLS** when the directory requires TLS before bind, or
when you will set passwords. Keep **Reject Unauthorized Certificates** on so Mindflow validates the server certificate
in production.

When certificate validation is on, the server certificate must be trusted and valid for the hostname in **Address**.

:::note[Private or enterprise CA]
If the directory uses a private or enterprise CA, ask Mindflow support to add your CA chain so certificate validation
succeeds.
:::

</li>
</ol>
### Identify a user or group

Most Active Directory actions identify an object either by **distinguished name (DN)** or by a lookup attribute such as
**sAMAccountName**. Prefer **DN** or **sAMAccountName**. Lookups by `mail` or `userPrincipalName` can be ambiguous if
multiple entries match.

- **By DN** — send only the distinguished name.
- **By lookup** — send **Type** and **Value**, with optional **Search base**.

**Search base:** omit it to search the domain default naming context; set it to restrict the search to that subtree.

After **Find user** or **Find group**, store the returned **DN** and reuse it in later steps.

**Type:** `sAMAccountName`

**Value:** `arivera`

**DN:** `CN=Alex Rivera,OU=Users,DC=example,DC=com`

### Active Directory operations

| Operation                  | Purpose                        | Typical identity     | Important behavior                                                                  |
| -------------------------- | ------------------------------ | -------------------- | ----------------------------------------------------------------------------------- |
| **Find user**              | Resolve one user               | DN or sAMAccountName | Returns the user record including **DN**; errors if none or several match           |
| **Find group**             | Resolve one group              | DN or sAMAccountName | Returns the group record including **DN**                                           |
| **Update user profile**    | Modify allow-listed attributes | DN preferred         | Omit = unchanged; `null` = clear                                                    |
| **Disable account**        | Disable login                  | DN preferred         | Flips only the disabled bit; preserves other `userAccountControl` flags; idempotent |
| **Enable account**         | Enable login                   | DN preferred         | Same flag behavior as disable; idempotent                                           |
| **Unlock account**         | Clear lockout                  | DN preferred         | Idempotent if already unlocked                                                      |
| **Set password**           | Write `unicodePwd`             | DN preferred         | Requires LDAPS or StartTLS; optional must-change-at-next-logon                      |
| **List user groups**       | List groups for a user         | DN preferred         | Direct `memberOf` **plus** primary group; optional nested ancestors                 |
| **Create user**            | Create under a parent OU       | Parent OU + CN + SAM | Created disabled; optional password then enable                                     |
| **Create group**           | Create under a parent OU       | Parent OU + CN + SAM | Scope and category required                                                         |
| **Add group member**       | Add to group `member`          | Group + member       | Idempotent if already a member                                                      |
| **Remove group member**    | Remove from group `member`     | Group + member       | Idempotent if already absent                                                        |
| **List group members**     | List member DNs                | Group identity       | Direct `member`, or nested when **nested** is on                                    |
| **Check group membership** | Whether a member is in a group | Group + member       | Returns `matched`; direct by default, nested when **nested** is on                  |

### Find a user

**Find user** finds one Active Directory user by DN or by lookup (`sAMAccountName`, `mail`, or `userPrincipalName`).
Prefer **sAMAccountName** or DN. Lookups by mail or UPN can be ambiguous if multiple entries match.

The step returns the user record, including the **DN** to map into later steps.

<ol>
<li>

**Add Find user.** Select the LDAP credential.

</li>
<li>

**Set the user identity.** Use DN or **sAMAccountName** as in [Identify a user or group](#identify-a-user-or-group).
Omit **Search base** unless you need to limit the subtree.

<div class="docs-screenshot-compact">

![Find user Input with By distinguished name and By lookup identity options on Find departing employee](./assets/find-user-identity.png)

</div>

</li>
</ol>
### Find a group

**Find group** finds one Active Directory group by DN or by lookup. Prefer **sAMAccountName** or DN. Returns the group
record, including the **DN**.

<ol>
<li>

**Add Find group.** Select the LDAP credential.

</li>
<li>

**Set the group identity.** Example:

**Type:** `sAMAccountName`

**Value:** `Security Analysts`

</li>
</ol>
### Update a user profile

**Update user profile** updates allow-listed attributes on one user in a single modify: `givenName`, `sn`,
`displayName`, `mail`, `department`, `title`, and `telephoneNumber`. Omit a property to leave it unchanged; send `null`
to clear it. Empty strings and an empty profile are rejected.

Use **Set password**, **Disable account**, or **Rename or move entry** for passwords, enablement, and moves.

<ol>
<li>

**Add Update user profile.** Select the LDAP credential.

</li>
<li>

**Set the user identity.** Prefer the DN from **Find user**.

</li>
<li>

**Set the profile.** Example:

**givenName:** `Alex`

**mail:** `alex.rivera@example.com`

**department:** `null` (clears the attribute)

</li>
</ol>
### Disable or enable an account

**Disable account** and **Enable account** flip only the disabled flag on `userAccountControl`. Other bits stay
unchanged. Already disabled or already enabled still succeeds. The resolved entry must be a user.

<ol>
<li>

**Add Disable account or Enable account.** Select the LDAP credential.

</li>
<li>

**Set the user identity.** Prefer the DN from **Find user**.

</li>
</ol>
### Unlock an account

**Unlock account** clears lockout on a user. Already unlocked still succeeds.

<ol>
<li>

**Add Unlock account.** Select the LDAP credential.

</li>
<li>

**Set the user identity.** Prefer the DN from **Find user**, or **sAMAccountName** `arivera`.

</li>
</ol>
### Set a password

**Set password** writes the directory password (`unicodePwd`). Requires **LDAPS** or **StartTLS**. Plain `ldap://`
without StartTLS is rejected.

Optional **Must change password** requires a password change at next logon (sets `pwdLastSet` to **0**).

<ol>
<li>

**Add Set password.** Select a credential whose Address is `ldaps://` or `ldap://` with **Use StartTLS**.

</li>
<li>

**Set the user and password.** Prefer DN or **sAMAccountName**, then set **Password**. Optionally turn on **Must change
password**.

</li>
</ol>
### List a user’s groups

**List user groups** returns groups for one user:

- Always includes direct `memberOf` memberships.
- Always includes the user’s **primary group**. Active Directory’s `memberOf` attribute does not contain the primary
  group; the connector adds it.
- When **nested** is on, also includes ancestor groups of those memberships (in-chain search). Groups are deduplicated
  by DN.

Search runs under the identity **Search base** when set, otherwise under the domain default naming context. Referrals
are not followed, so groups in other domains or forests are out of scope for this request. More than **10 000** groups
is rejected by the connector (**400**) without silent truncation.

The step returns the list of group records.

<ol>
<li>

**Add List user groups.** Select the LDAP credential.

</li>
<li>

**Set the user identity.** Prefer DN or **sAMAccountName** `arivera`. Turn **nested** on when you need ancestor groups.

</li>
</ol>
### Manage group membership

**Add group member** and **Remove group member** modify the group `member` attribute. Each step takes a **group
identity** and a **member identity** (user or nested group). Already a member or already absent still succeeds.

**List group members** returns member DNs. Direct membership reads `member` (with range retrieval when needed). When
**nested** is on, an in-chain search includes nested members.

**Check group membership** returns `{ matched: true | false }` without loading the full member list. Default is **direct**
membership only (`Compare` on `member`). Turn **nested** on for transitive (indirect) membership.

<ol>
<li>

**Add Add group member or Remove group member.** Select the LDAP credential.

</li>
<li>

**Set group and member.** Prefer **sAMAccountName** or DN for both:

**Group type:** `sAMAccountName`

**Group value:** `Security Analysts`

**Member type:** `sAMAccountName`

**Member value:** `arivera`

</li>
</ol>
### Create a user

**Create user** creates a user under `parentOu` with `cn` and `sAMAccountName` (maximum **20** characters). The DN is
`CN=<cn>,<parentOu>`. Do not send an existing-entry identity. Optional profile fields: `givenName`, `sn`,
`displayName`, `mail`, `userPrincipalName`.

The connector runs these stages in order on one bind:

1. Create the account **disabled**.
2. Set the password when **Password** is supplied (requires LDAPS or StartTLS).
3. Enable the account when **Enabled** is on.

**Enabled** without a **Password** still enables the disabled account after create; set a password separately if the
account must log on. **Must change password** applies only when a password is set.

The step returns the created user record, including the **DN**.

:::caution[Create can be half done]
If password or enable fails after the account is added, a **disabled** user entry may remain. Find it by
`sAMAccountName`, then **Delete entry** or finish **Set password** / **Enable account** before you retry Create.
:::

<div class="docs-screenshot-compact">

![Create user Input with Parent OU OU=Users,DC=example,DC=com, CN Alex Rivera, sAMAccountName arivera, Enable after create off](./assets/create-user.png)

</div>

<ol>
<li>

**Add Create user.** Select the LDAP credential. Set:

**Parent OU:** `OU=Users,DC=example,DC=com`

**CN:** `Alex Rivera`

**sAMAccountName:** `arivera`

</li>
<li>

**Optional: set password and enable.** Send a password only on LDAPS or StartTLS. Turn **Enabled** on when the account
should be usable immediately (after the password write when a password is set).

</li>
</ol>
### Create a group

**Create group** creates a group under `parentOu` with `cn`, `sAMAccountName` (maximum **20** characters),
`groupScope` (`global`, `domainLocal`, or `universal`), and `groupCategory` (`security` or `distribution`). Optional
`description`. Do not send an existing-entry identity. Returns the created group record, including the **DN**.

<ol>
<li>

**Add Create group.** Select the LDAP credential. Set:

**Parent OU:** `OU=Groups,DC=example,DC=com`

**CN:** `Playbook Operators`

**sAMAccountName:** `PlaybookOps`

</li>
<li>

**Set scope and category.**

**groupScope:** `global`

**groupCategory:** `security`

Then use **Add group member** when the flow should place users in the new group.

</li>
</ol>
### Use generic LDAP operations

The **LDAP** section is the lower-level alternative to named Active Directory actions. Use it for OpenLDAP, unusual
attributes, or filters those AD operations do not cover.

| Action                      | What it does                                                       |
| --------------------------- | ------------------------------------------------------------------ |
| **Search entries**          | RFC 4515 filter; one connector snapshot (up to **10 000** entries) |
| **Get entry**               | Read one entry by DN                                               |
| **Check entry exists**      | Whether a DN exists                                                |
| **Modify entry**            | Add, delete, or replace attributes                                 |
| **Add entry**               | Create under a parent DN                                           |
| **Rename or move entry**    | Change relative DN and optional parent                             |
| **Delete entry**            | Leaf only (missing leaf still succeeds; non-leaf fails)            |
| **Compare attribute value** | Assert a value without reading the full entry                      |

**Search entries** takes **Base DN**, **Filter**, optional **Scope**, optional **attributes** (omit to return all user
attributes; name operational attributes explicitly), and optional size limit. Results are one snapshot—not internally
paged beyond the connector’s **10 000**-entry cap.

| Scope  | Meaning                              |
| ------ | ------------------------------------ |
| `sub`  | Base entry and its subtree (default) |
| `one`  | Immediate children only              |
| `base` | Only the base entry                  |

<ol>
<li>

**Add Search entries** when you need a raw filter. Set the base DN and filter. Example by sAMAccountName:

```text title="Filter"
(sAMAccountName=arivera)
```

</li>
<li>

**Add Rename or move entry** to change OU. Set the current DN, the new relative DN, and the optional new superior.

</li>
</ol>
## Example

Create an **LDAP Simple Authentication** credential named **Corp AD bind** for `ldaps://dc01.example.com:636` as
`CN=mindflow-svc,OU=Service Accounts,DC=example,DC=com`.

In a flow titled **Offboard employee in directory**:

1. **Find user** with sAMAccountName `arivera`.
2. Map the returned **DN** into the next steps (reuse that DN; do not look the user up again by mail).
3. **List user groups** if the flow needs memberships for logging or decisions.
4. **Disable account** with that DN.
5. **Remove group member** for selected groups (for example `Security Analysts` only—removal is only what you
   configure, not every group automatically).
6. Optionally **Rename or move entry** into `OU=Disabled,DC=example,DC=com`.

<div class="docs-screenshot-compact">

![Offboard employee in directory flow with Find departing employee, List employee groups, Disable directory account, Remove from Security Analysts, and Rename or move entry](./assets/offboard-flow-canvas.png)

</div>

## Security and TLS

- Prefer `ldaps://` or StartTLS for production. Plain `ldap://` without StartTLS can expose the bind password.
- Keep **Reject Unauthorized Certificates** on in production. Ask Mindflow support to install a private or enterprise CA
  chain when needed.
- **Set password** and **Create user** with a password require encrypted LDAP.
- Use a least-privilege bind account for the operations your flow performs.

:::caution[Global Catalog]
To search the forest Global Catalog, set the credential **Address** to port **3269** (`ldaps://`) when using LDAP
Simple Authentication, or **3268** (`ldap://`) only if you accept the encryption trade-off.

The Global Catalog is read-only and exposes only the forest’s partial attribute set. Use GC ports for lookup and search,
not for create, update, password, enable/disable, membership changes, rename, or delete. Some attributes may be
unavailable from the Global Catalog.
:::

## Troubleshooting

| Symptom                    | Mindflow status | Typical cause                                                   |
| -------------------------- | --------------- | --------------------------------------------------------------- |
| No credential selected     | **401**         | Missing credential on the step                                  |
| Invalid Address or mapping | **400**         | Malformed LDAP config, bad request body, or connector limit hit |
| Ambiguous identity lookup  | **409**         | Several entries match (often shared mail or UPN)                |
| Wrong Bind DN or password  | **502**         | Upstream bind failure                                           |
| Connect timeout            | **504**         | Firewall, unreachable host, or step **Timeout** too short       |

The connector connects from the platform static egress IP on **TCP 389** or **TCP 636** (or **3268** / **3269** for
Global Catalog).

## Limits

These caps are enforced by the **Mindflow connector** (not every LDAP server):

| Limit                        | Value                    |
| ---------------------------- | ------------------------ |
| DN length                    | **2048**                 |
| Relative DN length           | **1024**                 |
| Filter length                | **4096**                 |
| Search / group-list snapshot | **10 000**               |
| Attribute value list         | **10 000**               |
| Create `sAMAccountName`      | **20** (AD create flows) |

A directory time, size, or administrative limit can also fail the step as **400**.

## Unsupported

- SASL binds other than simple authentication
- Arbitrary BER controls
- Server-side sort
- Subtree delete
- Following LDAP referrals — searches that need another naming context or server do not continue there automatically

Related: [Reach out to services](../) · [Call native integrations](../call-native-integrations/) ·
[Credentials](/getting-started/credentials/) · [Run remote commands with SSH](../run-remote-commands-with-ssh/)
