Skip to content

Outbound SCIM Connector

Built-in connector for provisioning and managing users + group memberships in any SCIM 2.0-compliant identity provider (Okta, Microsoft Entra ID / Azure AD, OneLogin, Keycloak, JumpCloud, PingOne, Auth0, etc.). Floh acts as the SCIM client; the IdP exposes the SCIM REST endpoints.

Pair this with the inbound SCIM endpoint when an IdP is the system of record for users coming into Floh, and use the outbound connector when Floh needs to push lifecycle events into a downstream IdP as part of an entitlement workflow.

When to Use

  • Provision a new hire into an IdP at the end of an onboarding workflow.
  • Grant or revoke an IdP group as the side effect of a Floh role grant / revoke (reconcilesWith on addGroupMember / removeGroupMember).
  • Deactivate a user in the IdP when their employment ends (soft-delete).
  • Sync user accounts from a SCIM IdP into Floh's resource catalog (cursor-paginated listUsers).

Prerequisites

  1. The target IdP must expose a SCIM 2.0 API. Most SaaS IdPs do; check the vendor's "SCIM provisioning" docs for the base URL.
  2. An API credential that the IdP accepts on its SCIM endpoint:
  3. A long-lived bearer token (Okta, OneLogin, JumpCloud, Auth0).
  4. Basic auth (rare; some self-hosted Keycloak deployments).
  5. OAuth2 client credentials (Microsoft Entra ID, PingOne, some Keycloak setups).
  6. Network egress from the Floh server to the IdP's SCIM host. The connector enforces SSRF protections — see Security & Networking below.

Connection Configuration

Create a connector instance via the Connectors API or UI with type scim.

Field Type Required Secret Description
baseUrl string Yes No SCIM v2 base URL (e.g. https://idp.example.com/scim/v2). Trailing slash is normalized.
authType select Yes No bearer, basic, or oauth2_client_credentials. Defaults to bearer.
bearerToken string When authType=bearer Yes Static bearer token issued by the IdP.
username string When authType=basic No Basic auth username. Must not contain : (RFC 7617 §2).
password string When authType=basic Yes Basic auth password.
oauth2TokenUrl string When authType=oauth2_client_credentials No OAuth2 token endpoint URL.
oauth2ClientId string When authType=oauth2_client_credentials No OAuth2 client id.
oauth2ClientSecret string When authType=oauth2_client_credentials Yes OAuth2 client secret.
oauth2Scope string No No Space-separated OAuth2 scopes requested at token issuance.
userResourcePath string No No Path appended to baseUrl for users. Defaults to /Users.
groupResourcePath string No No Path appended to baseUrl for groups. Defaults to /Groups.

Example: Okta (bearer token)

{
  "baseUrl": "https://example.okta.com/scim/v2",
  "authType": "bearer",
  "bearerToken": "00aBcDeFgHiJkLmNoPqRsTuVwXyZ1234567890ABCDEF"
}

Example: Microsoft Entra ID (OAuth2 client credentials)

{
  "baseUrl": "https://graph.microsoft.com/rp/scim",
  "authType": "oauth2_client_credentials",
  "oauth2TokenUrl": "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token",
  "oauth2ClientId": "<app-client-id>",
  "oauth2ClientSecret": "<app-client-secret>",
  "oauth2Scope": "https://graph.microsoft.com/.default"
}

Per the Microsoft Entra SCIM API reference, Entra exposes its SCIM endpoints under https://graph.microsoft.com/rp/scim. When integrating with a downstream SaaS app instead of Entra itself, use that app's documented SCIM base URL and bearer / OAuth2 credentials.

OAuth2 access tokens are cached on the connector instance for the lifetime of the workflow step and refreshed automatically when within 60 seconds of expiry.

Commands

All commands send and receive application/scim+json per RFC 7644 §3.1.

test

Verifies connectivity by issuing a GET /Users?count=1&startIndex=1.

Parameters: none.

Output: ok (boolean), baseUrl (string), authType (string), message (string).


User Lifecycle

createUser

Create a SCIM user. On 409 uniqueness (or any 409 when linkExistingOnConflict=true, the default), the connector falls back to GET /Users?filter=userName eq "..." and returns the existing record so workflow retries stay idempotent.

Parameter Type Required Default Description
userName string Yes - The SCIM userName (typically the user's primary email).
email string No - Primary work email. Emitted as emails[0] with type=work.
givenName string No - Given name (name.givenName).
familyName string No - Family name (name.familyName).
displayName string No - Display name.
externalId string No - SCIM externalId (use Floh's user id for traceability).
department string No - Enterprise extension department.
title string No - Enterprise extension title.
active boolean No true Initial active flag.
linkExistingOnConflict boolean No true If true, fall back to lookup-by-userName on 409 instead of failing.

Output: created (boolean), userId (string), userName (string), linkedExisting (boolean).

Reconciles with: checkUserActive — the entitlements engine can use this command in role-grant flows.

getUser

Fetch a user by id or by userName (one is required).

Parameter Type Required Description
id string Either SCIM resource id.
userName string Either SCIM userName to look up (filter).

Output: user (raw SCIM resource), userId, active.

updateUser

Update mutable user attributes. Defaults to a SCIM PATCH (RFC 7644 §3.5.2) with one replace op per supplied field. Set useReplace=true to issue a PUT with the full resource representation instead (PUT requires userName).

Parameter Type Required Default Description
id string Yes - SCIM resource id.
userName string Conditional - Required when useReplace=true.
email string No - Replace primary email.
givenName string No - Replace given name.
familyName string No - Replace family name.
displayName string No - Replace display name.
externalId string No - Replace externalId.
department string No - Replace enterprise extension department.
title string No - Replace enterprise extension title.
active boolean No - Toggle active flag.
useReplace boolean No false Issue a PUT with the full resource instead of PATCH.

A PATCH with no mutable fields fails fast with updateUser PATCH requires at least one mutable field so accidental no-ops are caught at the workflow boundary.

Output: updated (boolean), userId.

deactivateUser

Soft-delete via PATCH { op: "replace", path: "active", value: false }.

Parameter Type Required Description
id string Yes SCIM resource id.

Output: deactivated (boolean), userId.

Reconciles with: checkUserActive.

checkUserActive

Read the user's active flag. A 404 is treated as exists=false (idempotent reconciliation).

Parameter Type Required Description
id string Yes SCIM resource id.

Output: isActive (boolean), exists (boolean), userId.

listUsers

Cursor-paginated user list. SCIM startIndex / itemsPerPage are mapped onto the connector sync cursor surface so the resource sync engine can drive incremental syncs.

Parameter Type Required Default Description
cursor string No - Opaque cursor (decimal startIndex from the previous page).
pageSize number No 100 Max records per page (1 – 1 000).
filter string No - Optional SCIM filter (e.g. active eq true).
resourceType string No - Sync engine resource discriminator (set automatically when sync-driven).

Output: resources (array of { externalId, displayName, email, attributes }), nextCursor (string when more pages remain), totalEstimate (number when the IdP returns totalResults).

This command is syncCapable: true — wire it up via the Resource Sync UI to keep Floh's user catalog aligned with the upstream IdP.


Group Membership

addGroupMember

Add a user to a group via PATCH { op: "add", path: "members", value: [{ value: <memberId> }] }. Per RFC 7644 §3.5.2, SCIM servers MUST treat re-adding an existing member as a no-op, so this command is safe to retry.

Parameter Type Required Description
groupId string Yes SCIM group id.
memberId string Yes SCIM user id.

Output: added (boolean), groupId, memberId.

Reconciles with: checkGroupMembership.

removeGroupMember

Remove a user from a group via the SCIM filter form: PATCH { op: "remove", path: 'members[value eq "<memberId>"]' }. A 404 from the server is treated as already-removed for idempotency.

Parameter Type Required Description
groupId string Yes SCIM group id.
memberId string Yes SCIM user id.

Output: removed (boolean), groupId, memberId.

Reconciles with: checkGroupMembership.

checkGroupMembership

Read the group resource (?attributes=members) and scan the members array for memberId. Works against IdPs that don't expose a per-member endpoint. A 404 on the group is treated as isMember=false.

Parameter Type Required Description
groupId string Yes SCIM group id.
memberId string Yes SCIM user id.

Output: isMember (boolean), groupId, memberId.

Reconciliation Mappings

The following commands set reconcilesWith, so they integrate with the entitlement reconciliation surface:

Action command Reconciler Use in role mappings
createUser checkUserActive Provision identity → confirm activation.
deactivateUser checkUserActive Deactivate identity → confirm active=false.
addGroupMember checkGroupMembership Grant role → confirm group membership.
removeGroupMember checkGroupMembership Revoke role → confirm membership removed.

When you wire a Floh role to a SCIM group via link_config, the engine will automatically pick the matching reconciler so a manual reconcile/repair pass converges on the IdP's truth.

Security & Networking

The outbound SCIM client uses the same shared HTTP machinery as every other built-in connector:

  • SSRF protection — every outbound URL (SCIM base URL and OAuth2 token URL) is run through validateHttpConnectorUrl. Loopback, link-local, RFC 1918, carrier-grade NAT, cloud-metadata, and IPv6 ULA / link-local hosts are blocked.
  • DNS pinning — DNS-resolved hostnames are pinned to the resolved address for the lifetime of the request, preventing DNS-rebinding attacks between validation and connect.
  • No automatic redirects — 3xx responses surface as terminal errors. A validated public URL cannot be silently bounced to an internal target via Location:.
  • Sanitized logging — request URLs are sanitized via sanitizeUrl() before they hit the connector logger; bearer tokens, basic-auth credentials, and OAuth2 client secrets are never logged.
  • Bounded retries429 and 503 responses trigger up to 3 attempts total with capped exponential backoff (250 ms → 5 000 ms ceiling). The Retry-After header is honored when present (seconds or HTTP-date forms). All other non-2xx responses surface immediately as ScimOutboundError with statusCode, scimType, and responseBody available to errorMap.

Errors

All non-2xx SCIM responses become a ScimOutboundError, which the connector's errorMap translates into:

SCIM API error (<statusCode>): <detail-or-default-message>

Diagnostics returned to the workflow runner include:

Key Description
statusCode HTTP status of the SCIM response.
scimType The SCIM scimType from the response body when present (e.g. uniqueness, mutability, invalidValue).
responseBody Parsed JSON response body (no secrets — Floh logs are sanitized separately).

See Also