Microsoft Entra ID
When you're done, your tenant has one enterprise application for the desk:
- Only people assigned to it can sign in, and when you assign someone you pick one of the desk's roles: Agent, Organization Administrator, Change Manager, your organization's own roles. Platform Administrator is never on the list.
- The desk keeps that list of roles up to date on the application itself.
- The desk proves who it is with a certificate it makes and renews itself, so there is no client secret to copy or let expire.
- Optionally, Entra creates people in the desk as soon as they're assigned, and disables them when they're unassigned (step 6).
Each step below is done in the Microsoft Entra admin center. Under With the Azure CLI the same step is a few commands to copy and paste, one box at a time.
Before you use the Azure CLI
Sign in as an administrator who may grant admin consent. Cloud Shell in the Azure portal (Bash, No storage account required) has the CLI and is signed in already, but it needs an Azure subscription. Without one, install the CLI on your computer and sign in:
az login --tenant <tenant-id> --allow-no-subscriptions
The commands share a few names (DESK, APP, SP). In a new shell, set them again:
DESK=https://desk.example.com
APP=$(az ad app list --display-name "ITSM Engine" --query "[0].appId" -o tsv)
SP=$(az ad sp show --id $APP --query id -o tsv)
1. Create the application
- Enterprise applications → New application → Create your own application. Name it (like ITSM Engine) and pick Integrate any other application you don't find in the gallery (Non-gallery).
- Open it under App registrations → All applications. Authentication → Add a platform → Web, with the
redirect URI shown on the desk's Identity providers page (
https://<your desk>/api/v1/auth/sso/callback). - Token configuration → Add optional claim → ID → email, so the desk gets the email when it differs from the sign-in name.
- On Overview, note the Application (client) ID and the Directory (tenant) ID.
[!IMPORTANT] Make it under Enterprise applications as above, not under App registrations → New registration. Both sign people in, but only this one can provision people (step 6).
With the Azure CLI
Your desk's address:
DESK=https://desk.example.com
The application, from the non-gallery template:
APP=$(az rest --method POST --url https://graph.microsoft.com/v1.0/applicationTemplates/8adf8e6e-67b2-4cf2-a259-e3dc5476c621/instantiate --body '{"displayName":"ITSM Engine"}' --query application.appId -o tsv)
echo $APP
Wait half a minute, until Entra knows the new application everywhere. Then the redirect URI and the email claim:
az ad app update --id $APP --web-redirect-uris "$DESK/api/v1/auth/sso/callback" --optional-claims '{"idToken":[{"name":"email","essential":false}]}'
The two IDs for step 3:
echo "Directory (tenant) ID: $(az account show --query tenantId -o tsv)"
echo "Application (client) ID: $APP"
2. Let the desk keep its roles on the application
The desk writes its roles as app roles of the application through Microsoft Graph, and renews its certificate there. It needs a permission that lets it change only applications it owns, nothing else in your tenant, and it must own its own application.
- In the app registration: API permissions → Add a permission → Microsoft Graph → Application permissions → Application.ReadWrite.OwnedBy, then Grant admin consent.
- Make the application its own owner. The admin center's Owners page only offers people, so this one is always done with the Azure CLI (below, the last box).
With the Azure CLI
The permission, and the email permission for signing in:
GRAPH=00000003-0000-0000-c000-000000000000
OWNED_BY=$(az ad sp show --id $GRAPH --query "appRoles[?value=='Application.ReadWrite.OwnedBy'].id" -o tsv)
EMAIL=$(az ad sp show --id $GRAPH --query "oauth2PermissionScopes[?value=='email'].id" -o tsv)
az ad app permission add --id $APP --api $GRAPH --api-permissions $OWNED_BY=Role $EMAIL=Scope
Admin consent for both (az ad app permission admin-consent doesn't work in Cloud Shell, so one at a time):
SP=$(az ad sp show --id $APP --query id -o tsv)
az rest --method POST --url https://graph.microsoft.com/v1.0/servicePrincipals/$SP/appRoleAssignments --body "{\"principalId\":\"$SP\",\"resourceId\":\"$(az ad sp show --id $GRAPH --query id -o tsv)\",\"appRoleId\":\"$OWNED_BY\"}"
az ad app permission grant --id $APP --api $GRAPH --scope email
The application as its own owner:
az ad app owner add --id $APP --owner-object-id $SP
If you'd rather not grant the permission, turn off Keep this desk's roles as app roles in the desk, use Copy
appRoles JSON, and paste it as appRoles into the registration's Manifest. Do that again whenever roles change,
and upload the desk's next certificate yourself each year (The certificate).
3. Set it up in the desk
Platform → Identity providers → Microsoft Entra ID → Set up:
| Field | What to enter |
|---|---|
| Directory (tenant) ID, Application (client) ID | From step 1. |
| The desk proves who it is with | Certificate. (A client secret works too: make one under Certificates & secrets and enter it with its expiry date.) |
| Email domains | Your domains, like example.com. People with these emails sign in through Entra ID. |
| Organization | On a desk with several organizations: the one its people work in. |
| Create people at their first sign-in, Place them in | Someone assigned in Entra who isn't in the desk yet is created at their first sign-in, in this unit. |
| Keep this desk's roles as app roles | On, if you did step 2. |
Save. The desk makes its key pair and shows the certificate under Certificate.
4. Upload the desk's certificate
- Download certificate in the desk.
- In the app registration: Certificates & secrets → Certificates → Upload certificate.
- Back in the desk: Check with Entra ID. It says when Entra accepted the certificate, and Roles in Entra ID says when the roles were written (or what Graph refused).
With the Azure CLI
In Cloud Shell, upload the downloaded file first (Manage files → Upload). Then, with its name (--append keeps
the credentials already there):
az ad app credential reset --id $APP --cert @itsm-engine-1a2b3c4d.pem --append
5. Choose who may sign in
In Enterprise applications → your application:
- Properties → Assignment required: Yes. Only people assigned to the application can sign in.
- Users and groups → Add user/group: pick people or groups, then Select a role. That is the role they get in the desk.
With the Azure CLI
Assignment required:
az ad sp update --id $APP --set appRoleAssignmentRequired=true
Assign someone a role (the role's key, as in the desk's Roles, and their sign-in name):
ROLE=$(az ad app show --id $APP --query "appRoles[?value=='agent'].id" -o tsv)
az rest --method POST --url https://graph.microsoft.com/v1.0/servicePrincipals/$SP/appRoleAssignedTo --body "{\"principalId\":\"$WHO\",\"resourceId\":\"$SP\",\"appRoleId\":\"$ROLE\"}"
At each sign-in the desk gives the person the roles Entra assigns them, on the whole organization, and takes away roles Entra gave before but no longer does. Where someone works (units and teams) stays in the desk: someone new is placed in the unit you chose, and you move them under Users.
That's all for signing in. Step 6 is optional.
6. Provisioning (optional)
Without provisioning, someone appears in the desk at their first sign-in, and someone who leaves keeps their account until you disable it. With provisioning, Entra tells the desk straight away:
| In Entra | In the desk |
|---|---|
| Assigned to the application | Created in the unit chosen under Place them in, with the role of their assignment, before they ever sign in. Someone already in the desk with that email is linked, not made twice. |
| Role, name or email changed | Changed. Roles given by hand in the desk stay. |
| Unassigned, disabled or deleted | Disabled, and their sessions end. Nothing is deleted: their cases and history stay. |
| Assigned again, or restored | Enabled again. |
Connect Entra to the desk
- In the desk: Identity providers → Microsoft Entra ID → Provisioning → Turn on provisioning. Copy the token: it is shown once.
- In Entra: your application → Provisioning → Connect your application (or New configuration). Authentication
method Bearer authentication; Tenant URL as shown in the desk (
https://<your desk>/scim/v2), and the token as Secret token. - Test connection, then Create.
The token is a secret, so a person pastes it, even when the rest is done with the Azure CLI. Make new token in the desk replaces it (the old one stops working at once); Turn off ends provisioning and leaves everyone as they are.
Map the attributes
In Entra: your application → Provisioning → Attribute mapping.
- Provision Microsoft Entra ID Groups: off. The desk takes people, not groups. Assign a group to the application and its members are provisioned.
- Provision Microsoft Entra ID Users:
- externalId: change the source from
mailNicknameto objectId. The desk then knows the person by the same id as at sign-in, even before their first one and even when their email changes. - Roles: under Show advanced options → Edit attribute list for customappsso, add roles, type String,
Multi-Value. Then Add New Mapping: type Expression, expression
AppRoleAssignmentsComplex([appRoleAssignments]), target attribute roles. Without it the desk learns their role only when they sign in. - Leave userName ←
userPrincipalNameand emails[type eq "work"].value ←mailas they are. The desk needs an email: the work email, or a userName that is one. - Everything else (job title, phone numbers, address, manager …) is ignored. Delete it if you like.
- externalId: change the source from
With the Azure CLI
Fetch the mappings Entra made:
JOBS=https://graph.microsoft.com/v1.0/servicePrincipals/$SP/synchronization/jobs
JOB=$(az rest --url $JOBS --query "value[0].id" -o tsv)
az rest --url $JOBS/$JOB/schema > schema.json
Change them as above (groups off, externalId from objectId, roles added):
jq '(.directories[] | select(.name=="customappsso") | .objects[] | select(.name|endswith("enterprise:2.0:User")) | .attributes) += [{name:"roles",type:"String",multivalued:true,mutability:"ReadWrite",anchor:false,caseExact:false,required:false,flowNullValues:false,apiExpressions:[],metadata:[],referencedObjects:[]}]
| (.synchronizationRules[0].objectMappings[] | select(.name|endswith("Groups")) | .enabled) = false
| (.synchronizationRules[0].objectMappings[] | select(.name|endswith("Users")) | .attributeMappings) |= (map(if .targetAttributeName=="externalId" then .source={expression:"[objectId]",name:"objectId",type:"Attribute",parameters:[]} else . end)
+ [{targetAttributeName:"roles",flowType:"Always",flowBehavior:"FlowWhenChanged",matchingPriority:0,exportMissingReferences:false,defaultValue:null,source:{expression:"AppRoleAssignmentsComplex([appRoleAssignments])",name:"AppRoleAssignmentsComplex",type:"Function",parameters:[{key:"source",value:{expression:"[appRoleAssignments]",name:"appRoleAssignments",type:"Attribute",parameters:[]}}]}}])' schema.json > mapped.json
Save them in Entra:
az rest --method PUT --url $JOBS/$JOB/schema --body @mapped.json --headers Content-Type=application/json
Start it
- Provisioning → Provision on demand: pick one assigned person and check they appear in the desk.
- Start provisioning. The scope is Sync only assigned users and groups; Entra then provisions every 40 minutes.
With the Azure CLI
az rest --method POST --url $JOBS/$JOB/start
Provisioning never touches a Platform Administrator, someone who works in another organization, or someone named under Still sign in with a password in case Entra can't be reached; Entra's provisioning log says why. Creating or enabling someone takes a seat of the licence: with none left, Entra's log says so and nobody already active is affected. What the desk refused last is shown under Provisioning, and every change is in the audit log as made by Microsoft Entra ID provisioning.
The certificate
The desk makes an RSA key pair and a certificate valid for a year. The private key stays in the desk, encrypted, and
is never shown; only the certificate is uploaded to Entra. The desk signs in to Entra with a signed client assertion
(private_key_jwt), never a secret.
A month before the certificate runs out, the desk makes the next one and adds it to the application itself through Microsoft Graph, proving itself with the current one (the permission from step 2). Once the new one has worked, the old one is removed. Nobody has to do anything, and Renew now does it at once.
Without the Graph permission, the next certificate waits under Certificate with Download next certificate. Upload it like the first one; the desk takes it into use as soon as Entra accepts it, and the current one keeps working until then.
Access packages
Entra ID Governance's access packages work as they are: add the enterprise application to a catalog as a resource, and put one of its roles in an access package (Service desk agents → ITSM Engine, role Agent). People who get the package get the role, with the package's approval, expiry and access reviews. When the package ends, so does the role: within one provisioning cycle with step 6, otherwise at their next sign-in. With Assignment required they can't sign in at all.
Sign-in and provisioning read the same thing, the person's assignment on the application, so they never disagree: whichever reaches the desk first sets the role.
A role made in the desk shows in the catalog after the resource is refreshed there.
Troubleshooting
| On the sign-in page | Why |
|---|---|
| That account isn't from your organization's directory | The account is from another tenant than the one set up. |
| Your organization hasn't given you access | Assigned without a role (Default Access), and no role given in the desk. |
| There's no free seat for someone new | Every seat of the licence is used. |
| Your identity provider didn't complete the sign-in | Entra refused (often: not assigned, or a wrong redirect URI); the audit log has Entra's message. |
| On the Identity providers page | Why |
|---|---|
| Entra ID refused the certificate | It isn't uploaded to the application (step 4), or it was removed there. |
| The next certificate isn't on the application yet | The desk couldn't add it through Graph (step 2). Upload it by hand. |
| Provisioning: Last refused | What the desk refused last, as Entra's provisioning log shows it too. |
| In Entra's provisioning | Why |
|---|---|
| The Provisioning page says it isn't supported, Get started greyed out | The application was made under App registrations. Make one as in step 1, set it up in the desk again, and upload its certificate. |
| Test connection: 401 | The token is wrong or was replaced; make a new one in the desk. |
| A person fails with 403 | A Platform Administrator, someone in another organization, or no free seat: the error says which. |
| A person fails with 409 | Someone else is provisioned with that userName, or someone else in the desk has that email. |
| Saving the mappings: missing required parameter 'source' | The roles mapping's parameter must be named source. |