Authfu

Guide · Organizations

Directory sync with SCIM 2.0

Provision people and groups from Okta or Entra ID into an Authfu organization, and let the directory be the place where someone is switched off.

Connect a directory

  1. Create a directory connection token

    In the console open Organization settings → Directory sync → Connect a directory, pick Okta or Entra ID, and copy the token. It is shown once, stored hashed, and can be revoked from the same card. Directory sync is included from the Business plan.
  2. Paste two values into the directory

    Every product asks for the same two things — Authfu’s SCIM base URL and the token — under different names.
    text
    SCIM connector base URL   https://authfu.app/api/scim/v2
    Unique identifier field   userName
    Authentication Mode       HTTP Header
    Authorization             Bearer scim_…   (the token from the Authfu console)
    Okta: Applications → your app → Provisioning → Configure API Integration, then enable Create, Update, and Deactivate under To App. Entra: Enterprise applications → your app → Provisioning → Automatic, Test Connection, then turn provisioning on.
  3. Assign people and groups in the directory

    Anyone assigned becomes a member of the organization with the viewer role. Groups arrive as Authfu groups; assign a group to an application in the console and its members get that application’s tile — join the group in the directory, get the app.

What the directory controls

A SCIM User is an Authfu account plus its membership of your organization. The directory owns that membership’s lifecycle; it does not own the person’s Authfu account, which may also belong to other organizations.

  • Create — a new account is provisioned (or an existing account with that email is added to the organization). An open invitation to the same address is closed.
  • Update — name changes apply to members the directory created. The email address (userName) never changes through SCIM; that is a verified ceremony in Authfu.
  • Deactivate (active: false) — suspend and revoke credentials; access is kept for reactivation.
  • Delete — remove the membership and offboard. The account is never deleted.
  • The owner is listed but never mutable by a directory.

On the wire

Base URL https://authfu.app/api/scim/v2. Discovery (/ServiceProviderConfig, /ResourceTypes, /Schemas) is public; everything else needs the connection token as a bearer. Console sessions and API keys are refused on this surface so a leaked person credential can never drive provisioning.

http
POST /api/scim/v2/Users
Authorization: Bearer scim_…
Content-Type: application/scim+json

{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
  "userName": "jane@acme.com",
  "externalId": "00u1abc",
  "name": { "givenName": "Jane", "familyName": "Doe" },
  "active": true
}

→ 201 Created
{
  "id": "ckx…",
  "userName": "jane@acme.com",
  "externalId": "00u1abc",
  "active": true,
  "groups": [],
  "meta": { "resourceType": "User", "location": "https://authfu.app/api/scim/v2/Users/ckx…" }
}

Filters: userName eq, externalId eq, displayName eq, and emails[type eq "work"].value eq. Paging with startIndex (1-based) and count (max 200). Unsupported filters return 400 invalidFilterrather than an unfiltered list.

http
PATCH /api/scim/v2/Users/ckx…
Authorization: Bearer scim_…

{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [{ "op": "Replace", "path": "active", "value": false }]
}

→ 200 OK  { …, "active": false }
   Authfu: membership suspended, tokens and bound agents revoked, assignments kept.

PATCH accepts both dialects — Okta’s members path with a value list and Entra’s filtered path:

http
PATCH /api/scim/v2/Groups/ckg…

{
  "Operations": [
    { "op": "add",    "path": "members", "value": [{ "value": "ckx…" }] },
    { "op": "remove", "path": "members[value eq \"cky…\"]" }
  ]
}

Audit trail

Every change a directory makes is an audit event named scim.* carrying via: scim and the connection id — so a change made by Okta is distinguishable from one made by an admin in the console, and from one made through the API.

Next steps