# Configure LDAP Sources The worked example uses Microsoft Entra Domain Services. For AD DS or another compatible LDAP server, keep the same source structure and replace the endpoint, schema presets, and search base DNs. **Prerequisites:** - [Active Directory Plugin overview](https://docs.workflowengine.io/plugins/active-directory-plugin/overview/) - confirm that the LDAP integration boundary fits the application. - [Workflow Runtime](https://docs.workflowengine.io/get-started/concepts/workflow-runtime/) - configure the builder, persistence provider, and runtime services. - A license that includes the Active Directory feature. - A reachable LDAP endpoint and its directory schema and search base DNs. - A bind identity with the required read or write permissions when the server does not permit the required access without explicit credentials. ## Install the plugin package The `WorkflowEngine.NETCore-ActiveDirectoryPlugin` package supplies `ActiveDirectoryPlugin`, `ActiveDirectorySource`, and both built-in LDAP implementations. ```bash dotnet add package WorkflowEngine.NETCore-ActiveDirectoryPlugin ``` The package namespace is `OptimaJet.Workflow.Plugins.ActiveDirectoryPlugin`. ## Configure Microsoft Entra Domain Services [ActiveDirectorySource][api-active-directory-source] combines connection settings with a [user search definition][api-users-source] and a [group search definition][api-group-source]. `SetUsersSource(...)` and `SetGroupsSource(...)` configure those search mappings. For Microsoft Entra ID, enable Microsoft Entra Domain Services and secure LDAP first, then use the managed domain's LDAPS DNS name and port `636`. Microsoft's [LDAPS tutorial](https://learn.microsoft.com/en-us/entra/identity/domain-services/tutorial-configure-ldaps) covers the certificate, networking, and managed-domain prerequisites. Replace the example host, DNs, and bind identity with values from the managed domain. Supply the password from the application's secret provider. ```csharp filename="ActiveDirectoryRegistration.cs" using OptimaJet.Workflow.Core.Runtime; using OptimaJet.Workflow.Plugins.ActiveDirectoryPlugin; public static class ActiveDirectoryRegistration { public static WorkflowRuntime AddEntraDirectory( this WorkflowRuntime runtime, string bindPassword) { const string domainDn = "DC=aaddscontoso,DC=com"; const string directoryDn = "OU=AADDC Users,DC=aaddscontoso,DC=com"; var source = new ActiveDirectorySource( "entra-domain-services", "ldaps.aaddscontoso.com", 636, isSecureConnection: true) .SetAuth( "CN=Workflow Reader,OU=AADDC Users," + domainDn, bindPassword) .SetUsersSource(UsersSource.ADUserDn(directoryDn)) .SetGroupsSource(GroupSource.ADGroupDn(directoryDn)); return runtime.WithPlugin(new ActiveDirectoryPlugin(source)); } } ``` :::note Microsoft Entra Domain Services bind accounts The bind identity must be synchronized to the managed domain and have Kerberos and NTLM password hashes. A user created in Microsoft Entra ID is not synchronized to Domain Services until the user changes their password. Confirm that the bind account is in the synchronization scope, change its password when required, and wait for synchronization before testing LDAP authentication. See Microsoft's [credential synchronization behavior](https://learn.microsoft.com/en-us/entra/identity/domain-services/synchronization). ::: :::warning Do not write to the synchronized OU The example points both search bases to the synchronized `OU=AADDC Users` container. Use this source for authorization reads. To create or manage locally owned objects, configure another source whose search bases point to a writable custom OU. See [Manage Directory Users and Groups](https://docs.workflowengine.io/plugins/active-directory-plugin/manage-directory-users-and-groups/#protect-writable-directories). ::: ## Set the source connection `ActiveDirectorySource` stores connection and bind settings for one endpoint. Every configured source must also receive both a `UsersSource` and a `GroupSource`; accessing either property before setting it throws an exception. | Member or method | Default | Effect | |------------------|---------|--------| | `Name` | Required | Identifies the source in Designer values and runtime calls | | `Host` | Required | Sets the LDAP server DNS name or address | | `Port` | Required | Sets the LDAP port, commonly `636` for LDAPS | | `IsSecureConnection` | `false` | Enables the built-in client's secure LDAP mode | | `User`, `Password` | `null` | Supply the credential values passed to the selected directory client | | `SetAuth(user, password)` | Not called | Stores the bind identity and password passed to the selected directory client | | `SetUsersSource(...)` | Not set | Defines where and how users are read and created | | `SetGroupsSource(...)` | Not set | Defines where and how groups are read and created | ## Choose the LDAP implementation [LdapImplementation][api-ldap-implementation] is selected when `ActiveDirectoryPlugin` is created. `NovellLdap` is the cross-platform default. Use `DirectoryServices` only for a Windows-hosted application that specifically requires `System.DirectoryServices`. All sources in one plugin instance use the same implementation. See [Compare the LDAP implementations](https://docs.workflowengine.io/plugins/active-directory-plugin/overview/#compare-the-ldap-implementations) for differences in bind and lookup behavior. ## Protect credentials and network access `SetAuth` stores the credential strings on the in-memory source object. The plugin does not encrypt or persist that configuration. Keep the bind password outside source code, load it from the host application's secret provider, and do not log or serialize the source object. Grant the bind identity only the directory permissions required by the selected operations. Use LDAPS to protect bind credentials and directory traffic. The application host must trust the issuing certificate authority, and the endpoint hostname must match the certificate. Keep the LDAP endpoint on a private network when possible. If Microsoft Entra Domain Services secure LDAP is exposed over the public internet, restrict inbound TCP port `636` to known source IP addresses with network security group or firewall rules. ## Configure user and group searches [UsersSource][api-users-source] and [GroupSource][api-group-source] map the directory schema to the plugin models and define the bases used for reads and writes. ### Use explicit search base DNs Prefer the explicit DN factory methods so the configured search location is visible in code. | Factory | Search base | Schema defaults | |---------|-------------|-----------------| | `UsersSource.ADUserDn(usersDn)` | Uses `usersDn` exactly | `cn`, `memberOf`, object classes `user` and `person` | | `GroupSource.ADGroupDn(groupsDn)` | Uses `groupsDn` exactly | Filter `(objectClass=group)`, `cn`, `member` | | `UsersSource.LDAPUserDn(usersDn)` | Uses `usersDn` exactly | `cn`, `memberOf`, object classes `inetOrgPerson` and `person` | | `GroupSource.LDAPGroupDn(groupsDn)` | Uses `groupsDn` exactly | Filter `(objectClass=groupOfNames)`, `cn`, `member` | The base-DN convenience methods add fixed containers. `UsersSource.ADBaseDn(baseDn)` searches `CN=Users`, while `GroupSource.ADBaseDn(baseDn)` searches `CN=Builtin`. `UsersSource.LDAPBaseDn(baseDn)` adds `ou=users`, and `GroupSource.LDAPBaseDn(baseDn)` adds `ou=roles`. Use the explicit methods when the directory stores objects elsewhere, including `OU=AADDC Users` in Microsoft Entra Domain Services. The generic LDAP user preset also initializes `sn` from the requested user name during creation. The AD user preset defines no default attribute initializer. ### Map a custom schema For a custom schema, construct `UsersSource` or `GroupSource` directly and configure these members: | Configuration | Purpose | |---------------|---------| | `SearchBaseDn` | Sets the root for supported subtree searches and the parent DN used for created objects and constructed write-operation DNs | | `CommonNameAttribute` | Supplies the attribute mapped to the plugin object's `Name` | | `ObjectClasses` | Supplies object classes written when a user or group is created | | `UsersSource.MemberOfAttribute` | Names the user attribute that contains group DNs | | `UsersSource.LoadGroupMethod` | Chooses how the provider populates `ActiveDirectoryUser.Groups` | | `GroupSource.SearchFilter` | Filters group enumeration, including Designer autocomplete | | `GroupSource.MemberAttribute` | Names the group attribute that contains member DNs | | `Attributes` | Loads extra attributes and adds optional fields to Designer create operations | | `AttributeInitializers` | Calculates user attributes during creation; `NovellLdap` also applies group initializers, while `DirectoryServices` does not | When the plugin loads a user, it can build the user's group list in two ways: - The AD preset reads the groups listed in the user's `memberOf` attribute. - The generic LDAP preset searches the configured groups and checks their direct members for that user. This choice affects only the group list returned with a user. It does not change command authorization: the `ActiveDirectoryGroup` rule always checks the direct members of the selected group. ### Understand lookup and write locations Both providers create users and groups directly below their configured `SearchBaseDn` and construct DNs there for write operations. `DirectoryServices` also resolves name-based user and group operations by binding to that exact constructed DN. `NovellLdap` searches below the configured base for name-based reads, but its writes still target the constructed DN. Keep objects that the plugin manages directly below the corresponding base when behavior must be consistent across providers. ## Add more than one source `ActiveDirectoryPlugin` accepts a `List`. Each source name must be unique because runtime and Designer lookups use that name as the key. ```csharp filename="MultipleDirectoryRegistration.cs" using System.Collections.Generic; using OptimaJet.Workflow.Core.Runtime; using OptimaJet.Workflow.Plugins.ActiveDirectoryPlugin; public static class MultipleDirectoryRegistration { public static WorkflowRuntime AddDirectories( this WorkflowRuntime runtime, ActiveDirectorySource entraSource, ActiveDirectorySource partnerLdapSource) { var sources = new List { entraSource, partnerLdapSource }; return runtime.WithPlugin( new ActiveDirectoryPlugin( sources, LdapImplementation.NovellLdap)); } } ``` ## Verify the source configuration Open the Designer group selector and choose the configured source. Confirm that a known group appears in autocomplete. This verifies the endpoint, bind credentials, and group search settings. Workflow Designer autocomplete showing the admin group from the local-ldap source for an ActiveDirectoryGroup actor Group discovery does not verify user lookup, membership matching, or write access. Follow [Authorize Commands with Directory Groups](https://docs.workflowengine.io/plugins/active-directory-plugin/authorize-commands-with-directory-groups/) to verify authorization, or [Manage Directory Users and Groups](https://docs.workflowengine.io/plugins/active-directory-plugin/manage-directory-users-and-groups/#verify-a-management-workflow) to test a writable source. ## Troubleshoot source configuration `ActiveDirectorySource` failures usually come from endpoint security, a search base that does not contain the expected objects, or schema defaults that do not match the directory. | Symptom | Likely cause | Fix | |---------|--------------|-----| | TLS connection fails | The certificate is untrusted, expired, or does not match the LDAPS hostname | Install the required CA chain and use the certificate's DNS name | | Bind fails | The bind identity format, password, or network access is wrong | Test the same identity against the endpoint and confirm firewall rules | | No groups appear, or a named user is not found | The search base, common-name mapping, or group filter does not match the directory | Use explicit DNs and inspect the directory schema before changing preset fields | | Users load without groups | The directory does not populate `memberOf` as expected | Set `LoadGroupMethod` to `UseGroupSearch` and verify the group member attribute | | Registration reports a duplicate key | Two sources have the same `Name` | Give every `ActiveDirectorySource` a unique name | ## Frequently asked questions **Which LDAP implementation should I use on Linux?** Use `LdapImplementation.NovellLdap`. It is the Active Directory Plugin default and works across supported .NET platforms. See [Choose the LDAP implementation](#choose-the-ldap-implementation) for the Windows alternative. **Should I use `ADBaseDn` for Microsoft Entra Domain Services?** Usually no. Use `ADUserDn` and `ADGroupDn` with the actual managed-domain OUs, as described in [Use explicit search base DNs](#use-explicit-search-base-dns). **Can Active Directory Plugin use an anonymous LDAP bind?** With `NovellLdap`, omitting `SetAuth` passes unset credentials to the LDAP bind, so the server must permit that bind and access to every required attribute. `DirectoryServices` does not request an anonymous ADSI bind; with unset credentials, it uses the Windows caller's security context. `IsAnonymous` is changed only by `SetAuth` and does not change either provider's bind options. Call `SetAuth` when the application needs explicit and consistent credentials. **Can different sources use different LDAP implementations?** Not within one Active Directory Plugin instance. Its source-list constructor selects one `LdapImplementation` for the whole list. If one runtime must combine different directory backends, provide an `IActiveDirectoryMethods` implementation that delegates each source to the appropriate built-in provider. See the public-model limitation in the [management guide](https://docs.workflowengine.io/plugins/active-directory-plugin/manage-directory-users-and-groups/). [api-active-directory-source]: https://api.workflowengine.io/active-directory-plugin/OptimaJet.Workflow.Plugins.ActiveDirectoryPlugin/ActiveDirectorySource/ [api-users-source]: https://api.workflowengine.io/active-directory-plugin/OptimaJet.Workflow.Plugins.ActiveDirectoryPlugin/UsersSource/ [api-group-source]: https://api.workflowengine.io/active-directory-plugin/OptimaJet.Workflow.Plugins.ActiveDirectoryPlugin/GroupSource/ [api-ldap-implementation]: https://api.workflowengine.io/active-directory-plugin/OptimaJet.Workflow.Plugins.ActiveDirectoryPlugin/LdapImplementation/