Skip to content

Microsoft Entra ID connector (Floh → Entra ID)

Built-in connector type entra-id provisions and manages cloud users and groups in a Microsoft Entra ID (Azure AD) tenant through the Microsoft Graph v1.0 API, using the OAuth2 client-credentials grant (application permissions). It is the supported path for directory CRUD when a tenant does not expose a SCIM endpoint.

Use this connector (not the generic scim connector) for Floh → Entra ID user/group lifecycle. For inbound provisioning (Entra → Floh) use Floh's inbound SCIM endpoint (/scim/v2) from Entra's enterprise-application provisioning.

Implementation: packages/server/src/modules/connectors/handlers/entra-id.ts (+ entra-id-support/). App-registration setup is documented in Entra ID setup.

Connection configuration

Create a connector instance of type entra-id with these fields:

Field Type Required Secret Description
tenantId string Yes No Directory (tenant) ID — a GUID or a tenant domain (e.g. contoso.onmicrosoft.com). Wildcard authorities (common/organizations/consumers) are rejected.
clientId string Yes No Application (client) ID of the Entra app registration.
clientSecret string Yes Yes Client secret value for the app registration.
defaultDomain string No No UPN suffix used by createUser when userPrincipalName omits a domain (e.g. contoso.com).

Example

{
  "tenantId": "11111111-2222-3333-4444-555555555555",
  "clientId": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "clientSecret": "<client-secret-value>",
  "defaultDomain": "contoso.com"
}

Test: run the test command — it acquires a token and reads GET /organization, returning tokenObtained, tenantId, and organizationName. A 403 here usually means the application permissions have not been admin-consented yet (see setup).

Required application permissions

Grant these application permissions (not delegated) on the app registration and grant admin consent:

Permission Used by
User.ReadWrite.All user CRUD
User-PasswordProfile.ReadWrite.All setPassword / first_password_invitation — app-only passwordProfile updates require this in addition to User.ReadWrite.All
Group.ReadWrite.All group CRUD + membership
Directory.Read.All test connectivity / tenant-GUID resolution

Commands

Connectivity

Command Graph API Outputs
test GET /organization?$select=id,displayName tokenObtained, tenantId, organizationName

Users

Command Graph API Notes
listUsers GET /users Sync-capable. Optional OData filter; paginate with pageTokennextPageToken (Graph $skiptoken).
getUser GET /users/{userKey} userKey = object ID or UPN.
createUser POST /users userPrincipalName (domain auto-completed from defaultDomain if omitted), displayName, password (secret), optional mailNickname/givenName/surname/accountEnabled.
updateUser PATCH /users/{userKey} Any of displayName/givenName/surname/jobTitle/department/accountEnabled.
disableUser PATCH /users/{userKey} Sets accountEnabled: false.
deleteUser DELETE /users/{userKey} Soft delete — restorable for 30 days via Entra deleted items.
setPassword PATCH /users/{userKey} password (secret); never echoed in outputs. Compatible with the first_password_invitation step.

Groups

Command Graph API Notes
listGroups GET /groups Sync-capable; same pagination as listUsers.
getGroup GET /groups/{groupKey}
createGroup POST /groups groupType: security (default) or microsoft365. Static membership only (no dynamic rules).
updateGroup PATCH /groups/{groupKey} displayName and/or description.
deleteGroup DELETE /groups/{groupKey} Microsoft 365 groups soft-delete (30 days); security groups are removed immediately.
listGroupMembers GET /groups/{groupKey}/members Direct members; paginated.
addGroupMember POST /groups/{groupKey}/members/$ref Idempotent — an "already a member" 400 returns alreadyMember: true. reconcilesWith: checkGroupMembership.
removeGroupMember DELETE /groups/{groupKey}/members/{memberId}/$ref Idempotent — a 404 returns alreadyRemoved: true.
checkGroupMembership GET /groups/{groupKey}/members Direct-membership scan (case-insensitive GUID match). Bounded at 500 pages × 999 members (~499k); if a group exceeds that it fails with "undetermined" rather than a false negative — avoid for very large groups.

memberId for membership commands is the object ID (GUID) of the user or group to add/remove.

Resource sync

listUsers and listGroups are syncCapable. In sync mode they return normalized ConnectorResourceRecord rows and a nextCursor. User and group records carry attributes.identityIssuer = https://login.microsoftonline.com/<tenant-GUID>/v2.0; when the connector is configured with a tenant domain, the tenant GUID is resolved from /organization so the issuer matches the token tid used in identity matching. See Resource sync.

Migrating from the mock test-activedirectory connector

Workflow templates that target the in-memory test-activedirectory mock map to the following entra-id commands:

test-ad entra-id
findUser getUser (or listUsers with $filter)
createAccount createUser
enableAccount / disableAccount updateUser (accountEnabled) / disableUser
listUsers / listGroups listUsers / listGroups
getGroupMembers listGroupMembers
addToGroup / removeFromGroup addGroupMember / removeGroupMember
checkGroupMembership checkGroupMembership
setPassword setPassword

Limitations (v1)

  • Cloud directory only — no on-prem Active Directory / LDAP, hybrid-sync attributes, certificate auth, B2B guest invitation, license assignment, or dynamic group membership rules.
  • setPassword may fail for federated/synced users whose passwords are mastered on-premises; the Graph error is surfaced via errorMap.