System for Cross-Domain Identity Management (SCIM)

Pre-requisites

  1. You need to be Entra ID Admin

  2. You need to be Blockbrain Admin

Step 1 - Generate SCIM Token in Blockbrain

  1. Get your admin JWT (F12 for browser dev tools, when logged in, or similar)

  2. Send: Provider Key needs to be entra

    POST https://integrations.theblockbrain.ai/api/v1/scim/tokens
    Authorization: Bearer <tokenhere>
    Content-Type: application/json
    {
        "description": "Entra ID production sync",
        "provider": "entra"
    }
  3. Store the response, it'll be only shown once

    {
        "token": "82a236d740558501b824fd7ecabcb675b....",
        "description": "Entra ID SCIM provisioning",
        "provider": "entra"
    }

Step 2 - Create an Enterprise Application in Azure

  1. Go to Azure Portal → Entra ID → Enterprise applications

  2. Click New application → Create your own application

  3. Name it blockbrain (or your org's name), select "Integrate any other application you don't find in the gallery"

  4. Click Create

Step 3 - Configure Provisioning

  1. Go to Provisioning → Get started

  2. Set Provisioning Mode to Automatic

  3. Under Admin Credentials:

    1. Tenant URL, Value: https://integrations.theblockbrain.ai/scim/v2

    2. Secret Token, Value: <Token from Step 1>

  4. Click Test Connection — you should get a green success banner

  5. Click Save

Step 4 - Configure for Attribute Mappings

Entra's defaults work but need minor cleanup. Under Mappings, open Provision Entra ID Users.

Required user attributes (keep these):

Entra attribute
SCIM attribute

userPrincipleName

userName

IsSoftDeleted

active

mail

emails[type eq "work"].value

givenName

name.givenName

surname

name.familyName

displayName

displayName

objectId

externalId

Important: externalId mapped to objectId is what blockbrain uses for idempotency — if a user is re-provisioned, blockbrain finds the existing record by Entra object ID and returns 200 instead of creating a duplicate.

For Groups, open Provision Entra ID Groups:

Entra attribute
SCIM attribute

displayName

displayName

objectId

externalId

members

members

Entra automatically flattens nested group hierarchies server-side before sending — blockbrain's Entra adapter expects this and treats all members entries as direct user IDs.

Step 5 - Scope which users/groups get provisioned

Under Settings → Scope, choose one of:

  • "Sync only assigned users and groups" — recommended; you control who is provisioned by assigning them to this Enterprise App

  • "Sync all users and groups" — provisions your entire directory

To assign users/groups: go to Users and groups → Add user/group.

Step 6 - Start provisioning

  1. Under Provisioning, set Provisioning Status to On

  2. Click Save

  3. Click Provision on demand to test with a specific user before the full cycle runs

What happens when Entra provisions a user

Entra POST /scim/v2/Users → blockbrain checks MongoDB for existing userName or externalId (idempotent) → if new: creates identity in Zitadel, writes to MongoDB mapping_user → user auto-added to tenant's general group → effectiveRole computed and synced to Zitadel project grant → returns 201 (or 200 if already existed)

When a user is disabled in Entra (or removed from the app scope):

→ MongoDB status → "inactive" → Zitadel: POST /v2/users/{id}/deactivate → user can no longer log in to blockbrain

▎ Note: Entra sometimes sends active: "False" as a string — the handler covers both false (boolean) and "False" (string).

What happens when Entra provisions a group

→ blockbrain checks by externalId (idempotent) → creates group_user record in MongoDB with isAutoSync: true → members list contains Zitadel user IDs (Entra flattens nesting automatically) → returns 201

Groups in blockbrain default to the consumer role. To elevate a group to builder, admin, etc., update the group's role via the blockbrain admin interface — that role is then applied as the effectiveRole for all members.

Provisioning cycle

Entra runs an incremental sync every ~40 minutes. A full cycle runs every ~24 hours. You can trigger Provision on demand anytime for a specific user/group.

Troubleshooting

Symptom
Check

Test Connection fails 401

Token is wrong or provider mismatch — regenerate with "provider": "entra"

Test Connection fails 404

Tenant URL wrong — must end in /scim/v2 not /scim/v2/

Duplicate users

externalId → objectId mapping missing from attribute map

Role Provisioning

The core problem

SCIM has no standard for roles. Entra sends roles through an AppRoleAssignmentsComplex attribute using a non-obvious format. The code handles this in parseSCIMRoleValue inside repository.ts:9.

What Entra actually sends

When you assign an App Role to a user in Entra, it arrives in the SCIM PATCH body like this:

The value field is a stringified JSON object, not a plain string. The code at repository.ts:13 detects this, parses it, and extracts the inner value field ("admin") as the actual role key.

Entra may also send roles as an array of objects (without the filter path), in which case value[0].value is used.

Blockbrain role names

The valid roles are defined in role-engine.ts. They must match exactly (case-insensitive):

  • consumer

  • builder

  • pro-user

  • admin

  • superadmin

Anything that doesn't match falls back to consumer.

How to configure this in Entra

Step 1 — Define App Roles in your Enterprise Application

In Azure Portal, go to your Enterprise App → App roles → Create app role. Create one role per blockbrain role level:

Display name: Blockbrain Consumer Value: consumer Description: Read-only access Allowed: Users/Groups

Display name: Blockbrain Builder Value: builder ...

Display name: Blockbrain Admin Value: admin ...

The Value field is what blockbrain reads. It must be exactly consumer, builder, pro-user, admin, or superadmin.

Step 2 — Add the attribute mapping in Entra SCIM

In the Enterprise App → Provisioning → Attribute Mappings → Provision Entra ID Users, add a new mapping:

  • Mapping type: Expression

  • Expression: AppRoleAssignmentsComplex([appRoleAssignments])

  • Target attribute: roles

This is what causes Entra to send the stringified JSON object format the code expects.

Step 3 — Assign roles to users or groups

In the Enterprise App → Users and groups, when you assign a user or group, Entra will ask you to select a role. Pick the appropriate blockbrain role. Users with no role assignment get consumer by default.

Last updated