Directory Integrations
A directory integration connects nexusID to a system that already holds your users — LDAP, Active Directory, or a SCIM/database source — so accounts are created and kept in sync automatically instead of by hand.
Console View

Directory Integrations (/admin/directory-integrations) let nexusID synchronize users and
groups with external directories — inbound (LDAP/AD/SCIM/DB → nexusID) and, for AD/LDAP, outbound
(nexusID → AD/LDAP) as well.
Supported Directories
- LDAP / Active Directory: Connect via LDAP(S), either Direct (the broker binds itself) or Agent (the on-prem Sync Agent drains a write queue — air-gapped networks, no inbound firewall hole).
- SCIM 2.0: Act as a SCIM client to pull users from SCIM-compliant providers.
- Database: Import users from legacy SQL databases via JDBC.
Sync direction
- One-way Import (LDAP → nexusID): read-only. Never writes back, and is exempt from create-on-missing (an import-only source is never authoritative for what should exist).
- One-way Export (nexusID → LDAP): outbound writes only.
- Two-way Sync: reads inbound and writes outbound. Required for Deprovision on directory deletion (below).
Sync Features
- Scheduled Sync: an auto-sync interval (
autoSyncEnabled+syncIntervalMinutes), run by a ticker, plus on-demand Sync now. - Attribute Mapping: map external directory attributes to nexusID profile fields, and — for DIRECT outbound writers — map nexusID fields onto native AD/LDAP attributes.
- Computed (transform) outbound attributes: compose a native attribute from identity fields with a small expression language (below) — for values no straight 1:1 mapping can produce.
- Just-in-Time (JIT): create users on the fly during login (alternative to full sync).
- OU routing, correlation rules, forests: per-identity container placement, aggregated-account matching, and multi-domain forest reads/writes for DIRECT AD connectors.
Import Safeguards
TWO_WAY (or DIRECT create-on-missing) directories can act on what an inbound read tells them is absent — either side of the joiner/leaver relationship:
| Setting | Direction | Effect when an owned account is absent from the read |
|---|---|---|
Deprovision on directory deletion (deprovisionOnDeletion) | TWO_WAY only | Soft-deactivates the nexusID account (30-day reversible window) + enqueues a DISABLE lifecycle schedule. Okta/Entra-style "the directory deleted them, so we should too." |
Create account if missing in directory (createOnMissing) | DIRECT, any direction except One-way Import | (Re-)provisions the account: enqueues PROVISION then ENABLE. The mirror case — "the directory lost an account this source still says should exist." |
Both are off by default, scoped to accounts this integration owns (provenance-tagged to it — never the whole HR population), and guarded by the same two safeguards, framed after Okta Import Safeguards / Entra's deletion threshold:
- Empty-read abort — a read that returns zero users is treated as a partial/failed read,
never as "everyone was deleted." Nothing is deactivated or (re)created; the sync log records
deactivated=0(skipped:empty-read)/provisioned=0(skipped:empty-read). - Per-sync threshold —
deletionThreshold(delete side) /createThreshold(create side, new), default 10 each. If a single sync would act on more owned accounts than the threshold, the run HALTS and writes nothing for that side — surfaced asDirectorySyncStatus.PARTIAL_SUCCESSon the sync log with aBLOCKED(deletion-threshold: would deactivate N > threshold)/BLOCKED(create-threshold: would provision N > threshold)summary fragment. The read itself still succeeded — only the risky write action was withheld — which is why it'sPARTIAL_SUCCESS, notFAILED.
Admin console: on the Directory Integrations edit form,
- Two-way Sync direction exposes Deprovision on directory deletion → Deletion safeguard threshold (max per sync).
- Direct connection mode exposes Create account if missing in directory → Create safeguard threshold (max per sync).
Enable at most one per integration — they express opposite intents about which side of a disappearance is authoritative.
First-write gate and blast-radius preview
An integration that has never written to its directory is the riskiest one in the system — nothing
has yet proven the configuration is right. Turning on Require a preview before the first write
(requirePreviewBeforeFirstWrite) holds that integration's very first outbound write — create,
enable, disable, and attribute update all go through the same gate — until an admin has run a
preview and explicitly accepted it. Off by default so installs already past their first write are
never silently gated.
-
Admin → Directory Integrations → ⋯ → Preview.
POST /{id}/preview-syncruns a read-only walk over the population (DEFAULT_CAP2,000 identities per run; per-account detail capped at 5,000 rows) and aggregates it — the same shape as a bulk update, so a bad default is unmissable in aggregate even when no individual preview would raise it:telephoneNumber 351 accounts → "0001"
department 12 accounts → various -
The result reports, per attribute, the accounts affected and whether every one would land the same value — the "uniform value" signal that flags a placeholder/bad-default rewrite — plus separate counts for existing objects that would be rewritten, identities that would be created (no directory object yet — shown with their full create payload, not an empty diff), and anything a provisioning policy suppresses (routes elsewhere) rather than counting as a real create/update.
-
Once the run finishes, Accept and allow writes clears the gate (
POST /{id}/acknowledge-first-write, audited with the acting admin and timestamp). It applies once — after the first acknowledgement the integration writes normally. -
Re-arming: if a later edit broadens what the integration can write (adds to
syncAttributes/attributeMapping/computedAttributes, or changessyncDirection) after the gate was already cleared, the acknowledgement is cleared too and a fresh preview is required — logged asDIRECTORY_WRITE_AUTHORITY_CHANGEDwithfirstWriteGateReArmed. Narrowing what it writes does not re-arm it.
Computed (transform) outbound attributes
For a native attribute that isn't a straight 1:1 copy of one identity field, compose it from several fields plus literals with a small expression language (shared between per-integration computed attributes and Provisioning Rules's create-time fields):
$jobTitle + " - " + $department + " - " + $manager.displayName
$jobTitle + " - " + case($company, "Public Broadcasting Service", $department,
"PBSD", "PBS Distribution") + " - " + $manager.displayName
$firstName + " " + optional($middleName:initial + ". ") + $lastName
→ "John M. Smith" (or "John Smith" when there's no middle name)
- Terms are joined with
+: a$variable(optionally piped through functions,$middleName:initial), a"quoted literal", acase(...)lookup, or anoptional(...)group. - Functions:
initial,upper,lower,trim,clean(letters+digits only),first3. Chainable ($middleName:initial:upper); an unrecognized function name passes the value through unchanged rather than eating data. case(selector, match1, result1, match2, result2[, default])is a lookup table, not a general conditional — matches compare case-insensitively, and only the selected branch is resolved, so an unresolvable value on a branch a given identity never takes (e.g.$departmenton a population that always hits the"PBS Distribution"literal) doesn't blank the whole value for everyone else.optional(...)emits its composed content only when every variable inside it resolves non-blank, and the empty string otherwise — so an absent middle name drops just its own group instead of voiding the composed value.- If any referenced variable is unresolved, the entire computed value is skipped for that
write (never a half-formed
"Engineer - - ") — logged, and visible in the preview before anything is written. - A short blocklist of AD attributes can never be targeted this way regardless of mapping —
credentials (
unicodePwd,userPassword, …),userAccountControl(owned by lifecycle), security posture (nTSecurityDescriptor,sIDHistory,objectSid,adminCount,servicePrincipalName), group membership, and structural attributes (objectClass,distinguishedName, …).sAMAccountName,userPrincipalName,mail, andcnare not blocked. - Computed attributes are resolved after 1:1 attribute mapping and before per-field transforms, and now appear identically on both the create preview and the update preview — a computed value used to show correctly on update-preview but silently disappear from create-preview; the create preview now reuses the exact payload a real create would write.
Related
- Provisioning Rules — data-defined generation of the create-time username / email / UPN / display name, keyed by source.
- Reconciliation — verify a target still matches nexusID after the fact.
- Sync Agent — the on-prem AD/DB connector for AGENT-mode integrations.
- HR Sources — the joiner/mover/leaver feeds that drive provisioning to a directory integration.