> For the complete documentation index, see [llms.txt](https://docs.nullify.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.nullify.ai/configuration/account-and-teams/entra-sso-and-scim.md).

# Microsoft Entra ID SSO & SCIM

Connect your Microsoft Entra ID (Azure AD) directory to an existing Nullify workspace so members sign in with their Microsoft work account, optionally provision them with SCIM, and optionally require Microsoft sign-in instead of GitHub. Setup takes about ten minutes once a Microsoft administrator is available.

{% hint style="info" %}
This is not the Azure DevOps install. A new Azure DevOps workspace starts from the [setup invitation](/enterprise-tier/azure-install.md#set-up-a-new-workspace). That setup creates the first administrator and links the directory. It does not install Azure DevOps, and it does not turn on Microsoft-only sign-in. Come back to this page after the workspace exists, when you want SCIM or to require Microsoft sign-in for everyone.
{% endhint %}

This guide covers Microsoft OIDC sign-in for a workspace that already has members, including a GitHub-backed workspace. Connecting identity does not connect Azure DevOps, change your repository provider or grant Azure DevOps access. SAML is not offered.

## Before you start

* **Application.** Nullify owns one official multitenant application: **Nullify**, client ID `37d9fe25-ca83-49ac-a099-054de06b1aad`, owned by directory `44859d25-e6d6-42d9-a98d-e5df5cbc4ac0`. You do not create an SSO app registration or send Nullify a secret. Signing in at [app.nullify.ai](https://app.nullify.ai/) uses this application. If **Settings → Identity** does not offer **Connect Microsoft Entra ID**, stop and contact Nullify before starting directory consent. Admin consent creates its enterprise application in your directory, requesting only the OpenID Connect sign-in scopes `openid profile email` (with `offline_access`, which Microsoft adds to every consent page). Microsoft lists these under **Microsoft Graph** in the permissions blade; they give Nullify no access to Graph data, and the application requests no Azure DevOps permission.
* **Nullify permission.** You need identity-management permission in Nullify. **Settings → Identity** shows the controls disabled without it.
* **Microsoft role.** Someone must approve the application for the whole directory with one of: Global Administrator, Privileged Role Administrator, Application Administrator or Cloud Application Administrator. Microsoft also lets AI Administrator and custom roles with consent permissions grant consent, but Nullify does not accept them: they finish Microsoft's step and then see `not_directory_admin`. If the role is eligible in Privileged Identity Management, activate it first; Nullify asks Microsoft for a fresh sign-in so the activated role is seen. Approving connects every Microsoft sign-in from the directory to your workspace, so any holder of these roles can do it; Nullify tells its own staff about every connection.
* **What Microsoft shows.** The application is not yet publisher verified, so Microsoft shows **Unverified** beside the client ID above, and the permissions *Sign you in*, *View your basic profile*, *View your email address* and *Maintain access to data you have given it access to*. Microsoft does not block an administrator from approving an unverified application, but your organization's own policies may: the admin consent workflow, app consent policies, Defender for Cloud Apps policies, or Conditional Access (AADSTS53003). If Microsoft shows **Request approval** instead of **Accept**, the account cannot grant consent: a reviewer's approval grants the consent but does not connect Nullify, so once it is approved ask for a new link (or select **Connect** again). Keep those controls; if one blocks approval, have your security team review an exception for this exact client ID.
* **One to one.** A Nullify workspace connects to one Entra directory, and a directory to one workspace.

## Connect

### A. You are also a Microsoft administrator

1. In **Settings → Identity**, select **Connect Microsoft Entra ID**.
2. Microsoft asks you to approve the application for your directory. Check the client ID above, then approve.
3. Microsoft asks you to sign in again with the same directory administrator account.
4. You return to **Settings → Identity** with **Microsoft Entra ID connected**.

### B. Someone else is the Microsoft administrator

1. In **Settings → Identity**, under **Ask your Microsoft administrator**, enter your directory (tenant) ID, from the Microsoft Entra admin center under **Overview**, and select **Create consent link**.
2. Copy the link now; it is shown once. Send it only to your Microsoft administrator. It works once, expires after 24 hours, and creating another link replaces it. The panel lists the live link and lets you revoke it.
3. The administrator opens the link. The page names your Nullify workspace, who asked and the directory, and says what Microsoft will show; nothing happens until they tick the approval box and continue. They approve the application, then sign in again.
4. Nullify then shows them their own account, the directory and your workspace once more, and connects the directory only when they select **Connect the directory**. They get a confirmation page and, when Microsoft gives Nullify their email address, an email receipt that says how to undo it.
5. Your Identity card shows **Connected** within about 15 seconds.

The link stops working if the person who created it can no longer manage the workspace's identity settings (removed, deactivated or demoted) before it is used; create a new one.

Connecting adds sign-in for the directory. It does not change existing members, roles, sessions or GitHub sign-in.

## If it fails

| Message or code                                                                   | What to do                                                                                                                                                     |
| --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Cannot grant admin consent for its directory (`not_directory_admin`)              | Sign in with one of the roles above; activate an eligible PIM role first.                                                                                      |
| Admin consent was not granted (`consent_declined`)                                | Retry and approve the organization-wide consent.                                                                                                               |
| Different directory than the one that granted consent (`directory_mismatch`)      | Sign in both times with an account in the directory being connected.                                                                                           |
| Already connected to a different directory (`directory_conflict`)                 | Disconnect the current directory first.                                                                                                                        |
| Directory already connected to a different Nullify workspace (`directory_in_use`) | Contact <support@nullify.ai>; staff can release a directory connected to the wrong workspace.                                                                  |
| A sign-in policy change is in progress (`policy_in_progress`)                     | Finish or retry the Microsoft sign-in policy change first.                                                                                                     |
| The link's creator can no longer manage the workspace (`creator_revoked`)         | Ask a current workspace administrator for a new link.                                                                                                          |
| Could not connect (`error`)                                                       | Retry; if it persists, contact Nullify with the time.                                                                                                          |
| AADSTS50105                                                                       | The user is not assigned to the application; assign them or allow all users.                                                                                   |
| AADSTS65004                                                                       | Consent was declined.                                                                                                                                          |
| AADSTS90094                                                                       | Your directory requires administrator approval; use an administrator account.                                                                                  |
| AADSTS90095                                                                       | The admin consent workflow intercepted the request. A reviewer's approval grants consent but does not connect Nullify; ask for a new link once it is approved. |
| AADSTS53003                                                                       | Conditional Access blocked the sign-in; check the policy with your security team.                                                                              |

## Link administrators and members

Connecting a directory does not link anyone's existing Nullify account. A member who signs in with Microsoft before linking sees that they have no role yet; they should sign in with GitHub instead and link. Each person who signs in with GitHub today opens **Settings → Identity** and selects **Link my Microsoft account**, signing in with their own work account in the directory. That binds their Microsoft identity to their existing Nullify account and keeps its role and teams. Matching email addresses are not enough. Link at least two workspace administrators before requiring Microsoft sign-in.

### Activate existing members before linking

An account synced from GitHub can have a role without being activated. Linking that account does not activate it: Microsoft access is denied until the member is activated, even when ordinary GitHub access works. Changing a role in **Vault → Access → Roles** does not activate the account either.

An authorized Nullify workspace administrator with identity-management, user-management and role-assignment permissions must explicitly activate the intended members. In the rebuilt frontend, go to **Vault → Access → Roles** and select **Activate access** for one inactive member. Confirm the member and their existing role; activation preserves their teams. Wait for the confirmed active state before linking and testing Microsoft sign-in.

If your deployment does not yet show **Activate access**, use `POST /admin/users/activate` on the workspace API with your authenticated workspace session, or ask your Nullify representative to perform this step. This endpoint does not create users or link Microsoft identities.

Get the existing workspace user IDs from `GET /admin/access/users`. Confirm the approved member roster, role and teams before submitting the activation request; this endpoint does not create users. For example:

```json
{
  "users": [
    {
      "userId": "<existing-nullify-user-id>",
      "role": "builtin:admin",
      "teamIds": []
    }
  ]
}
```

Choose `builtin:admin`, `builtin:editor` or `builtin:viewer` for each selected member and include their approved team IDs when needed. Do not send the entire synced roster or promote members just to make sign-in work. Verify the response and each member's activated state, then link and test Microsoft access. Prepare two explicitly approved administrators before requiring Microsoft sign-in. SCIM provisioning with an active user and one valid role is an alternative activation path; do not create a duplicate account for an existing member.

Before enforcing SSO, inventory every member who needs access. Each must link their existing account while GitHub sign-in is allowed, or be SCIM-provisioned with an active identity binding and valid role. An unknown Microsoft sign-in does not automatically create workspace membership; GitHub account linking is unavailable once Entra-only access blocks that session. Link at least two active Nullify administrators, then test their Microsoft sign-in separately. A directory administrator role grants consent authority in Microsoft; it does not grant the Nullify workspace administrator role.

Once the directory is connected and access is linked or provisioned, members sign in from the Nullify login page by choosing the Microsoft Entra ID option. They land in the Nullify tenant that directory is connected to.

Signing in with Microsoft only authenticates the user. Their access and role in Nullify come from SCIM provisioning (below) or from your tenant's access settings. See [Account Access and Teams](/configuration/account-and-teams.md).

A person whose organization has no Nullify workspace can still sign in with Microsoft. They see that their sign-in worked: if their organization already uses Nullify, its administrator connects the Microsoft directory in **Settings → Identity**; otherwise Nullify follows up. Nullify records their request from their Microsoft sign-in (their user principal name, or the directory when there is none) and is notified; they can correct their contact details and say whether their organization uses Azure DevOps. Signing in creates no workspace, role or data. Once Nullify has set up the workspace and the directory is connected, the same account signs in again with no re-registration.

Guests of your directory (B2B accounts from another organization, personal Microsoft accounts, social or one-time-passcode accounts) cannot sign in to Nullify through it; they see a sign-in error and must use an account from their own organization's directory. Members who sign in through your directory's own federation (AD FS, Okta) can sign in too: Nullify's app registration requests the `acct` ID-token claim, which tells members and guests apart, so you configure nothing for it. Provision guests by SCIM only if they genuinely need access; they still cannot sign in until they use a member account.

## SCIM provisioning

SCIM lets Microsoft Entra, or another identity system, create, update and deactivate Nullify users and set their role. Connect your directory first; SCIM is rejected until then.

### Generate a credential

1. In **Settings → Identity**, find **SCIM provisioning** and copy the **SCIM base URL** exactly. It is your workspace's own API host.
2. Select **Generate credential** and copy the `nscim_` token. It is shown once; Nullify stores only its hash.
3. The card shows when your identity system last called Nullify (updated within five minutes). To rotate, generate a new credential, update your identity system, confirm requests succeed, then revoke the old one. Revoking a credential stops provisioning; it does not deactivate users already provisioned.

Paste the token only into your identity system's secret field. The workspace IP allowlist does not restrict SCIM bearer requests.

### Set up provisioning in Microsoft Entra

Microsoft does not enable automatic provisioning on the enterprise application created by consenting to an app registration, so provisioning uses a second, provisioning-only application. Members keep signing in through the **Nullify** application.

1. **Enterprise applications → New application → Create your own application**, name it "Nullify provisioning" and choose **Integrate any other application you don't find in the gallery**. Leave single sign-on unconfigured.
2. In **App registrations**, open that application and add app roles with the values `admin`, `editor` and `viewer` (exactly, lower case), allowed for users and groups. Assign each user or group exactly one role.
3. Open the provisioning application's **Provisioning** page and select **+ New configuration**. Enter the SCIM base URL as **Tenant URL** and a new `nscim_` credential as **Secret Token**, select **Test Connection**, then **Create**.
4. **Attribute mapping**: turn off **Provision Microsoft Entra ID Groups**. In the user mappings set `externalId` to `objectId` (required: the default `mailNickname` is rejected), keep `userName` mapped to `userPrincipalName`, and add `roles[primary eq "True"].value` mapped to the expression `SingleAppRoleAssignment([appRoleAssignments])`.
5. Set the scope to **Sync only assigned users and groups**. Under **Overview → Properties**, add a notification email and turn on accidental deletion prevention.
6. Use **Provision on demand** for one pilot user and check them in **Vault → Access → Roles**, then **Start provisioning**. The first cycle takes longer; after that Microsoft syncs about every 40 minutes.

Assignment rules that decide who gets which role:

* **Assign the same users and groups to both applications.** With **Assignment required** on the **Nullify** application, someone assigned only to **Nullify provisioning** is provisioned but cannot sign in (AADSTS50105).
* **One role per person.** If a person holds several app roles (for example through two groups), Microsoft gives no guarantee which one `SingleAppRoleAssignment` sends, so they could be made an administrator at random. Make group role assignments mutually exclusive.
* **Groups need Microsoft Entra ID P1 or P2.** Without it, assign users individually. Nested groups are not expanded; assign the group that holds the members directly.
* Changing a person's role sends Nullify a role PATCH that adds the new role and removes the old one; Nullify ends with the new role in either order.

An identity lifecycle tool (for example Saviynt) can use the same SCIM URL and credential instead; see [Entra groups driving Nullify roles](/configuration/account-and-teams/entra-group-role-provisioning.md).

### Supported operations

Nullify supports the SCIM 2.0 **Users** resource. There is no Groups endpoint.

| Operation      | Endpoint                               |
| -------------- | -------------------------------------- |
| Create a user  | `POST /Users`                          |
| Get a user     | `GET /Users/{id}`                      |
| List users     | `GET /Users` with an optional `filter` |
| Update a user  | `PATCH /Users/{id}`                    |
| Replace a user | `PUT /Users/{id}`                      |
| Remove a user  | `DELETE /Users/{id}`                   |

* **Filters:** `userName eq "..."` and `externalId eq "..."`.
* **PATCH:** `add`, `replace` and `remove` on `active`, `roles` (including `roles[primary eq "True"].value` with a single role string, role objects whose `primary` is `"True"`, and role values sent as a JSON string), `userName`, `externalId`, `name` (and `name.givenName` / `name.familyName`), `displayName` and `emails`. `remove` on `roles` with a value removes only the listed roles; without a value it removes all of them.
* **Discovery:** `GET /ServiceProviderConfig` advertises PATCH and filtering (up to 200 results). Bulk operations, sorting, ETags and password changes are not supported. `/ResourceTypes` and `/Schemas` are also available.

### Required attributes

| Attribute    | Requirement                                                                                                                                                                                                                                                                                                                                                    |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `userName`   | Required on create; include it in a full PUT replacement. Use the agreed login name, normally the user's work UPN, which may differ from their contact email. An omitted or empty PUT value preserves the existing username; PATCH can omit it to preserve the existing value.                                                                                 |
| `externalId` | Required on create. Must be the user's Entra **object ID** (a GUID), not their UPN, mail nickname or display name. Nullify uses it to match Microsoft sign-in to provisioned access. PUT or PATCH may omit it to preserve the existing identity. Do not rebind an established record to a different person; use the approved new-user and offboarding process. |

### Roles

Send the user's Nullify role in `roles`, for example `"roles": [{ "value": "editor" }]`.

For creation and replacement with POST or PUT:

| `roles` value                                | Result                                                                                                                         |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| Exactly one of `admin`, `editor` or `viewer` | Role assignment is activated; access also requires an active lifecycle (`active` defaults to `true` on POST/PUT when omitted). |
| Empty or omitted                             | User is stored as `viewer` but **not activated**.                                                                              |
| Two or more roles, or any other value        | Request rejected with `400 Bad Request`.                                                                                       |

PATCH preserves the current role when `roles` is omitted. Operations apply to the user's current role in order: replace sets it, add adds a role (when that leaves two, the role just added wins), and remove takes away the listed roles. A role-only PATCH preserves the user's lifecycle state; it does not reactivate a deactivated user. Use one approved role and `active: true` to restore access.

### Deactivating and removing users

* `active: false` deactivates the user. They can no longer access the tenant.
* `active: true` restores access only when the user also has a valid assigned role.
* `DELETE /Users/{id}` removes the user's access. Nullify keeps the user record.

### Teams

SCIM does not assign teams. Where team-scope enforcement is enabled for the workspace and configured for an editor or viewer, access is limited to that user's assigned teams. A Nullify administrator must assign those teams in Nullify. Microsoft sign-in does not enable team-scope enforcement; confirm the workspace rollout and test both allowed and excluded resources. See [Account Access and Teams](/configuration/account-and-teams.md#teams-and-ownership).

## Network restrictions

In **Settings → Network**, an administrator can limit the IPv4 and IPv6 ranges people reach the workspace from. Enter **every** egress range your proxy or secure web gateway vendor publishes, not just the address you see today: cloud gateways and NAT pools move people between egress nodes, and a list that matches only your current node can lock people out later. Saving first previews what the list does (for example no IPv6 range, a private range or a single host), then asks you to type your current IP address and waits ten seconds before you can confirm. A saved change reaches every request within about a minute. Nullify refuses a list that excludes your own address, and refuses ranges that together match every address, such as `0.0.0.0/0` or `::/0` (save an empty list to remove the restriction). Add your IPv6 egress range if your network uses IPv6; browsers may switch to it. Service accounts and GitHub Actions are exempt. Nullify support is also blocked from your dashboard by the list. If you lock yourself out, another administrator on an allowed network can change it, or Nullify support can clear it.

## Require Microsoft sign-in

This turns off GitHub sign-in for the workspace. Members must sign in with Microsoft through the connected directory; dashboard, CLI and MCP sessions started with GitHub lose access on their next request. Anyone who signs in with GitHub afterwards sees **This workspace requires Microsoft sign-in** with a **Sign in with Microsoft** button. Service-account tokens, GitHub Actions, SCIM, the GitHub App and repository scanning are not affected. Authorised Nullify staff keep access to the workspace for support, including when signed in with GitHub.

1. Link or provision every member who needs access. Nullify refuses to turn this on until at least **two** active administrators have proven a Microsoft sign-in: either they linked their Microsoft account, or they were provisioned by SCIM and have since signed in to Nullify with Microsoft once. A SCIM-provisioned administrator who has never signed in does not count, because SCIM cannot tell a member from a guest.
2. Sign out and sign in with Microsoft as a workspace administrator. The change needs a Microsoft sign-in from the last **five minutes**, so you cannot lock yourself out by enabling it from a GitHub session.
3. In **Settings → Identity**, turn on **Require Microsoft sign-in**, enter a reason and select **Check change**. The check shows how many administrators are linked and how many active members have no linked Microsoft account and would lose access.
4. Select **Require Microsoft sign-in**. Confirm in a separate session that a linked member keeps access and a GitHub session does not.

While it is on, Nullify refuses a role change, unlink, deactivation or erasure made in Nullify that would leave fewer than two administrators who have proven a Microsoft sign-in; add a replacement first. Only deprovisioning from Microsoft through SCIM takes effect below that floor, so a leaver loses access immediately, and Nullify staff are alerted to help you restore a second administrator.

To allow GitHub sign-in again, a Microsoft-signed-in administrator turns the switch off, checks and confirms. If no Microsoft administrator can sign in, contact Nullify: staff can turn it off. Every change is audit-logged.

## Disconnect and release

Turn off **Require Microsoft sign-in** first; disconnect is blocked while it is on. Then select **Disconnect** on the Identity card. The directory binding is removed, SCIM credentials are revoked and Microsoft sign-in to the workspace ends; user records are kept.

If your directory is connected to a workspace you do not recognise (`directory_in_use`), remove the Nullify enterprise application from your directory and contact <support@nullify.ai>. Nullify staff can release the directory so your own workspace can connect it.

## Limitations

* **No gallery provisioning template yet.** Use the provisioning-only application above.
* **No group sync and no automatic team assignment.** Only users are provisioned.
* **Removed users** may see the Nullify onboarding page when they sign in, rather than a message that their access was removed.
* **Guests** cannot sign in through the directory; see above.
