> For the complete documentation index, see [llms.txt](https://docs.stackai.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.stackai.com/welcome-to-stackai/security-and-governance/security-in-stackai/system-for-cross-domain-identity-management-scim/scim-through-entra.md).

# SCIM through Entra

This guide covers the **core SCIM setup**: enabling SCIM in Stack AI, creating the Entra enterprise app, configuring attribute mappings for users, assigning users, and verifying that provisioning works.

***

### Prerequisites

* Stack AI organization admin access
* Microsoft Entra tenant with privileges to create Enterprise applications and edit App Registrations
* A modern browser tab open to each side

***

### Step 1 — Enable SCIM in Stack AI

1. Sign in to Stack AI as an org admin.
2. Open **Settings → SCIM Provisioning**.
3. Click **Enable SCIM** and confirm. This creates a `scim_provisioning_configs` row for your org and unlocks the SCIM endpoints.
4. From the **Overview** card, copy two values — you'll need them on the Entra side:
   * **Base URL** — looks like `https://api.stack-ai.com/scim/v2`
   * **SCIM token** — click **Generate token**, give it a name (e.g. "Entra Production"), and copy the token immediately. **You only see it once**; if you lose it, regenerate.

Leave this page open — you'll come back to verify provisioned users.

***

### Step 2 — Create the Enterprise application in Entra

1. Open the [Microsoft Entra admin center](https://entra.microsoft.com/).
2. **Identity → Applications → Enterprise applications** → **+ New application**.
3. Click **+ Create your own application**.
4. Name it (e.g. `Stack AI`).
5. Choose **"Integrate any other application you don't find in the gallery (Non-gallery)"** → **Create**.

Entra will build the app from its generic SCIM template — that's why provisioning logs and job names will reference `customappsso`. It's cosmetic and expected.

***

### Step 3 — Configure SCIM credentials and test the connection

1. In the new app, open the **Provisioning** blade → **Get started**.
2. Set **Provisioning Mode** = **Automatic**.
3. Expand **Admin Credentials**:
   * **Tenant URL**: paste the Stack AI Base URL from Step 1 (`https://api.stack-ai.com/scim/v2`)
   * **Secret Token**: paste the SCIM token from Step 1
4. Click **Test Connection**. Entra calls `/ServiceProviderConfig` and `/Users?count=1`. Both should return 200.
   * **401**: token is wrong or expired — regenerate in Stack AI and re-paste.
   * **404**: Tenant URL is wrong — double-check the trailing `/scim/v2`.
   * **5xx**: Stack AI side issue; check Stack AI status before continuing.
5. Click **Save**.

***

### Step 4 — Configure user attribute mappings

After saving credentials, the **Mappings** section appears with two object types: **Users** and **Groups**. Start with users — groups are covered in the optional section at the end.

1. Click **Provision Microsoft Entra ID Users**.
2. **Enabled**: Yes.
3. **Target Object Actions**: leave Create / Update / Delete all checked.
4. Verify (and trim) the attribute mappings. The minimum useful set:

   | Microsoft Entra attribute                                     | Target SCIM attribute          | Notes                           |
   | ------------------------------------------------------------- | ------------------------------ | ------------------------------- |
   | `userPrincipalName`                                           | `userName`                     | Identity key                    |
   | `Switch([IsSoftDeleted], , "False", "True", "True", "False")` | `active`                       | Disables suspended users        |
   | `objectId`                                                    | `externalId`                   | Stable lookup; survives renames |
   | `mail`                                                        | `emails[type eq "work"].value` | Required for invites/SSO match  |
   | `givenName`                                                   | `name.givenName`               |                                 |
   | `surname`                                                     | `name.familyName`              |                                 |
   | `displayName`                                                 | `displayName`                  |                                 |

   You can delete the rest of Entra's defaults (manager, addresses, phone numbers, etc.) — Stack AI ignores them and removing them reduces sync errors.
5. Save the mappings.

***

### Step 5 — Assign users to the app

Provisioning **scope** defaults to **"Sync only assigned users and groups"**. Leave this — broader scopes provision your entire tenant, which you almost never want.

1. In the same Enterprise application, open the **Users and groups** blade.
2. **+ Add user/group** → pick users (or, later, groups — see the optional group section).
3. Save.

Only assigned users are eligible for provisioning. Adding more users later is non-destructive — they get picked up on the next sync cycle.

***

### Step 6 — Start provisioning and verify

1. Back to the **Provisioning** blade.
2. Click **Start provisioning** at the top. Status should switch to **Provisioning** (green).
3. To skip the wait, use **Provision on demand**:
   * Click **Provision on demand**
   * Search for one of the assigned users → **Provision**
   * Inspect the result. The "Modify user" or "Create user" step shows the exact SCIM payload sent and the response received.
4. In Stack AI → **Settings → Members**, confirm the user appears with the expected name, email, and active state.

Going forward, Entra runs a sync cycle every **\~40 minutes**. This interval is set by Microsoft and cannot be lowered.

***

### What to expect operationally

* **Adding a user to the app** → next cycle they appear in Stack AI.
* **Removing a user from the app** (or soft-deleting them in Entra) → `active=false` is sent, Stack AI deactivates them. The user record is preserved.
* **Hard-deleting a user in Entra** after 30 days → SCIM `DELETE /Users/{id}` is sent and the user is removed from Stack AI.
* **Renaming a user** → Stack AI updates the existing record, identified via `externalId`. No duplicate is created.
* **Quarantine** (red banner on the Provisioning blade) → too many failures or auth errors. Fix the underlying issue, then **Restart provisioning** to clear it.

***

### Troubleshooting checklist

If provisioning isn't behaving:

1. **Provisioning logs** (Provisioning blade → "View provisioning logs"). Filter by Status = Failure. Each row shows the request, response, and exact error.
2. **401 Unauthorized** on every call → token expired or revoked in Stack AI. Regenerate and update Entra credentials.
3. **403 Forbidden** on `/Users` → SCIM was disabled on the Stack AI side. Re-enable from the SCIM Provisioning page.
4. **400 invalidValue** on `userName` → almost always a missing `userPrincipalName` for that user. Check the user's profile in Entra.
5. **No log rows at all for a user you expected** → they're not assigned to the app, or they're in an unassigned group.
6. **Provision on demand** is the fastest debug tool. Use it after every config change to validate without waiting for the cycle.

***

## Optional: Group sync

By default, Stack AI mirrors Entra groups one-to-one — same name, same membership. There's no group → role / group → permission translation; the synced group is just a Stack AI group. Skip this section if you only need user provisioning.

### Enable group sync in Stack AI

1. Stack AI → **Settings → SCIM Provisioning**.
2. Toggle **Sync groups** to **ON**.

If this toggle is off, every Entra `/Groups` call returns **403 Forbidden** and Entra logs will be flooded with group-create failures.

### Confirm the Group mapping is enabled in Entra

1. Provisioning blade → **Mappings**.
2. Click **Provision Microsoft Entra ID Groups** → **Enabled = Yes**.
3. Verify the minimum mapping set:

   | Entra attribute | SCIM attribute |
   | --------------- | -------------- |
   | `displayName`   | `displayName`  |
   | `objectId`      | `externalId`   |
   | `members`       | `members`      |

   The Entra group's `displayName` becomes the Stack AI group name verbatim. Pick clean, human-readable group names in Entra; they show up directly in Stack AI's UI.

### Assign the group to the app

In **Users and groups**, **+ Add user/group** → pick the **group itself** (not just its members). Assigning a member's parent group does not implicitly provision the group; the group must be assigned explicitly.

> **Note**: Entra does **not** expand nested or dynamic groups for SCIM. If you have a group of groups, assign each leaf group separately.

### Verify

After the next sync (or via **Provision on demand** on the group):

* The group appears in Stack AI → **Settings → Groups**.
* Members are populated. Adding/removing a user from the Entra group propagates as a SCIM `PATCH` (add/remove members) on the next cycle.
* Renaming the Entra group updates the Stack AI group's name (it's matched by `externalId`).
* Deleting the Entra group removes the Stack AI group; member users are kept.

***

## Optional: Role mapping

By default, provisioned users have **no role**. To grant roles automatically, you create a translation table in Stack AI ("when Entra sends role X, set Stack AI role Y") and configure Entra to send a role value per user.

### How role resolution works

When Stack AI receives a SCIM user payload with a `roles` array, it:

1. Picks the role marked `primary: true` (or the first if none flagged).
2. Lowercases and trims the value.
3. Looks for an exact match in your role mappings.
4. If no match → assigns the configured **default role**.
5. If there's no default role → user is provisioned with no role.

Matching is case-insensitive, but whitespace and unicode quirks count. Use plain lowercase tokens (`admin`, `member`, `viewer`).

### Step A — Create role mappings in Stack AI

1. Stack AI → **Settings → SCIM Provisioning** → **Role mappings** card.
2. **Set a default role** in the SCIM config (highly recommended). This is the safety net for users without a matching role.
3. **+ Add mapping** for each label you intend to send from Entra:

   | IdP role value | Stack AI role |
   | -------------- | ------------- |
   | `admin`        | Admin         |
   | `member`       | Member        |
   | `viewer`       | Viewer        |

### Step B — Define App Roles on the App Registration

App Roles live on the **App Registration**, not the Enterprise application.

1. Entra admin → **App registrations** → open the registration that backs your Stack AI enterprise app (same name).
2. **App roles** → **+ Create app role** for each Stack AI role:
   * **Display name**: e.g. `Stack AI Admin`
   * **Allowed member types**: Users/Groups
   * **Value**: `admin` ← this is the string sent in `roles[].value`. Must match the `idp_role_value` from Step A.
   * **Description**: anything
   * **Enable this app role?**: Yes
3. Repeat for `member`, `viewer`, etc.

### Step C — Assign users (or groups) to App Roles

1. Back to the **Enterprise application** → **Users and groups**.
2. **+ Add user/group** → pick a user or group → **Select a role** → choose the app role → **Assign**.
3. Each user can hold **one** app-role assignment per app. Re-assigning replaces the previous role.
4. If you assign a group, every member inherits the role for SCIM purposes.

### Step D — Map App Roles into the SCIM `roles` attribute

Provisioning blade → **Mappings** → **Provision Microsoft Entra ID Users**:

1. Scroll to the bottom and tick **Show advanced options**.
2. Click **Edit attribute list for customappsso**.
3. Ensure `roles` exists with **Type = String** and **Multi-Valued = True**. Add it if missing. Save.
4. Back in the user mappings list, **+ Add New Mapping**:
   * **Mapping type**: Expression
   * **Expression**: `SingleAppRoleAssignment([appRoleAssignments])`
   * **Target attribute**: `roles[primary eq "True"].value`
   * **Match objects using this attribute**: No
   * **Apply this mapping**: Always
5. Save.

The `SingleAppRoleAssignment` function emits the user's current app-role **Value** (e.g. `"admin"`), placed into `roles[0].value` with `primary=true` — exactly what Stack AI's resolver looks for.

### Step E — Verify

1. **Provision on demand** for a user with an assigned app role.
2. Inspect the outbound SCIM payload — `roles` should be present with the right value.
3. In Stack AI → **Settings → Members**, the user's role should reflect the mapping.

### Troubleshooting role mapping

* **`roles` missing from payload** → Step D-3 (schema) wasn't saved, or the user has no app role assigned (Step C).
* **`roles` present but Stack AI uses the default** → label mismatch. The `Value` on the App Role and the `idp_role_value` in Stack AI must match (case-insensitive, no extra whitespace).
* **Existing users not updating** → they don't re-resolve until something on their record changes. Either trigger a Provision on demand for each, or wait for the next sync cycle and ensure their app-role assignment was set/changed (which forces an update).
* **Multiple app-role assignments** → not supported by `SingleAppRoleAssignment`. Use only one app role per user.

### Lightweight alternative

If you don't want to manage App Roles, set a single constant role for everyone:

* In **Mappings → Provision Microsoft Entra ID Users**, **+ Add New Mapping**:
  * **Mapping type**: Constant
  * **Constant value**: `member`
  * **Target attribute**: `roles[primary eq "True"].value`
* Add one matching role mapping in Stack AI: `member` → Member.

Everyone gets the same role on provisioning; admins are promoted manually inside Stack AI. Simpler, less Entra to manage, but no role differentiation from the IdP.

***

### Quick reference

| Question                                | Where it's configured                                                                                          |
| --------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| Who can SSO into Stack AI?              | Entra app → Users and groups (assignment), SSO blade (provider config)                                         |
| Who gets provisioned?                   | Entra app → Users and groups (must be assigned **and** in Provisioning scope)                                  |
| What attributes flow over?              | Entra app → Provisioning → Mappings                                                                            |
| Where is my SCIM token?                 | Stack AI → SCIM Provisioning page (regenerate if lost)                                                         |
| Why isn't my group syncing?             | (1) `Sync groups` toggle in Stack AI, (2) group itself assigned to the app, (3) Group mapping enabled in Entra |
| Why is everyone getting the wrong role? | Role mapping label mismatch, or no `roles` claim in SCIM payload                                               |
| How fast does a change propagate?       | Every \~40 minutes, or instantly via **Provision on demand**                                                   |


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.stackai.com/welcome-to-stackai/security-and-governance/security-in-stackai/system-for-cross-domain-identity-management-scim/scim-through-entra.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
