Workflow approvals
The Approval step stops a mesh workflow until a person decides. The run waits while the approvers are told by an inbox notice and an email. They approve or reject on the Approvals page of the portal. Approved continues the run. Rejected or timed out either ends the run, or continues into a branch that does something else.
Approvals are part of mesh workflows, so they need the Enterprise tier. This runbook covers the step’s fields, who can approve, what approvers see, what operators can do, and a worked example: manager approval before an account is enabled.
Add an Approval step
In the workflow designer (Configure → Workflows, then New workflow or Edit), drag Approval from the Control group of the palette onto the canvas. Select the card to fill in its fields.
| Field | What it does |
|---|---|
| Approvers | Who decides. Required. Pick them with the same Objects field as other steps: Related objects (for example the relationship manager, Objects this one points to), Objects matching a query (people, or a group), or Objects by id (expression). See Choosing the objects a step acts on. The object itself is refused: the object cannot approve its own workflow. |
| Escalate to | Optional. A second set of approvers, asked as well when nobody has decided by Escalate after, or at once when none of the first set can act. Required when Escalate after is set. |
| Approvals needed | How many approvals settle the request. Default 1, minimum 1. Any single rejection rejects. |
| Escalate after | Optional. A duration such as 2.00:00:00. Must be shorter than Time out after. |
| Time out after | How long to wait for a decision. Default 7.00:00:00, at least 1 minute, at most 400 days. |
| Title | Optional. The line approvers see in the list and the notice subject. It can contain tokens such as {{displayName}}. Default: the workflow name and the object’s name, such as New joiner approval: Ada Lovelace. |
| Message to approvers | Optional. Plain text shown to approvers, with tokens such as {{department}}. A sensitive attribute shows as <sensitive>. |
| When rejected | end (default) ends the run as Rejected. continue runs the next step, which can branch on the outcome. |
| When timed out | end (default) ends the run as TimedOut. continue runs the next step. |
Both durations are written as days.hours:minutes:seconds and count
from the moment the request is opened. The 400-day limit is the
Workflows:MaxDelayDays setting, shared with the Wait step; see
Tuning.
Validate checks the step: approvers set, durations in range, Escalate after shorter than Time out after, and an escalation set present when Escalate after is.
Branch on the outcome
When When rejected or When timed out is continue, the
properties panel shows Insert outcome fork. It adds two nested
If/else branches right after the Approval
step:
| Branch | Condition | Then | Else |
|---|---|---|---|
| Approved? | EqualsIgnoreCase(attr("steps.<key>.outcome"), "approved") | The request was approved. | Goes to Rejected?. |
| Rejected? | EqualsIgnoreCase(attr("steps.<key>.outcome"), "rejected") | The request was rejected. | The request timed out. |
<key> is the Approval step’s key. The arms start empty; drag the
steps you want into them. With When timed out left on end, a
timed-out run ends before the fork, so the last Else arm is never
reached.
Who can approve
Approvers are mesh objects, not typed user names. The portal links a
signed-in person to their mesh object by matching the sign-in against
the object’s userPrincipalName, mail or sAMAccountName.
- A person can act when one of those three attributes matches exactly one live person object. A person who cannot is still listed on the request, marked no portal identity, and is never counted.
- A group as an approver is not asked itself. Its direct members are asked, up to the first 100, and any one of them can decide. A member of a nested group is not asked.
- The person the workflow runs for can never approve it. They are dropped from every approver set, and their own decision is refused, even by an operator.
- Each person decides once. One rejection rejects the request. It is approved when the number of approvals reaches Approvals needed.
- The threshold never waits on a vote that cannot come. If fewer people can act than Approvals needed, the threshold is lowered to the number who can, and the request carries a note saying so. When escalation adds people, it rises back toward the configured value.
- Escalation adds, it does not replace. After escalation, the first set can still decide beside the escalation set. People in the escalation set cannot decide before the request escalates.
- Nobody can act. If none of the first set can act, the request escalates at once when Escalate to has someone who can. Otherwise it times out at once with the reason “no approver could act”, and When timed out decides what happens to the run.
Other object types found by the approver target are skipped with a note.
How approvers are told
Each approver who can act gets an inbox notice under the bell and, if email is set up and their object has an email address, an email. The messages are plain text.
| When | Subject |
|---|---|
| The request opens | Approval: title (needs your approval) |
| The request escalates (to the escalation set) | Approval: title (escalated to you) |
| Someone approves or rejects it | Approval: title (approved), or Approval: title (rejected) |
| Nobody decided in time | Approval: title (timed out) |
| The run ended first | Approval: title (withdrawn) |
The body starts with “Workflow:” and the workflow’s name, then “About:” with the object it is about and its type, the message, how many approvals there are so far (“Approvals so far: n of m”), and, for a new request, when it escalates and times out in UTC. Group join and vault requests use the same form with their own prefix; see Notifications. Only approvers are told. To tell anyone else, such as the person or HR, add a Send email step in the outcome branch.
The link in the email
The last line of the email is a link to the Approvals page when the
Portal URL is set under Settings → Email, in the Links
section. The link opens /#/identity/approvals. Without a Portal URL,
the email ends with “Open the IdentityMesh portal and go to
Approvals.” See Email.
The Approvals page
Approvals in the sidebar, its own entry directly below Mesh Objects, at /identity/approvals.
It is the one shared page for every kind of approval: it lists
workflow requests beside group join requests and vault check-out
requests, with a kind filter. The old address
/identity/workflows/approvals redirects to it with the Workflow
kind preset. The page, its columns and the shared rules are described
in Approvals. Deciding a workflow request needs the
workflows.approve permission, which every built-in role except
Viewer has. On a tier below Enterprise the page says “Approvals
require the Enterprise tier”.
| Tab | Shows |
|---|---|
| To decide | Open requests you can decide now. |
| Decided by me | Requests you have decided. |
| All open | Every open workflow request. Only for holders of workflows.execute. |
| Completion failed | Requests whose follow-up action failed. A workflow request has no follow-up action of its own, so it never appears here. |
A workflow row’s Title is the step’s Title, and its Subject is the object the run is for.
To decide, select Approve or Reject on the row. A dialog opens with a Reason (recorded in the audit trail) box. Confirm with Approve or Reject. The run picks the decision up within seconds.
A request that was already settled, a vote that crossed with someone else’s, or a subject that no longer exists is refused, with the reason. Reload the page to see where it stands.
What the run shows
While it waits, the run’s status is Delayed. It does not hold up other runs for the same object. The run detail shows a Waiting for approval panel with:
- the title and “approvals n of m”, with an escalated badge when it has escalated,
- every approver, their tier, a no portal identity mark where it applies, and each decision with its reason,
- Escalates and Times out with their times.
“Delayed until” on the run shows the next deadline. After the decision, the step’s outputs are on the run detail and the path the run took through the outcome fork is painted.
A run that ends because of the request shows Rejected or TimedOut, with the decision under Outcome, such as “Rejected by ada.lovelace@contoso.com” or “Timed out (timed out)”.
Outputs
| Output | Contains |
|---|---|
outcome | approved, rejected or timedOut. |
approvedBy | The sign-in names of the people who approved, joined with ;, in the order they decided. After an operator approval, the operator’s sign-in name. |
decidedBy | Who settled the request. Empty when it timed out. |
decidedUtc | When it was settled or timed out, in UTC. |
reason | The reason given with the settling decision, or timed out. |
approvals | How many people approved. |
escalated | true or false. |
approvalId | The request’s id. |
A later step reads them as steps.<key>.<output>, for example
{{steps.managerApproval.approvedBy}} in an email body.
Operator actions
Holders of workflows.execute can settle a request without being an
approver. An operator decision settles it outright, whatever Approvals
needed says, and the approvers are told it was decided by an
administrator.
| Where | Action | Effect |
|---|---|---|
| Run detail of a run waiting for approval | Approve as operator | Asks for a reason, then approves. Replaces Skip step and Resume now for these runs. |
| Approvals page, All open tab | Approve as operator / Reject as operator | On rows you are not an approver on. On rows where you are an approver, Approve and Reject cast your own vote. |
| Run detail | Cancel run | Withdraws the request. The approvers are told it was withdrawn. |
Skipping the step through the API counts as an operator approval with the reason “skipped by operator”. The person the workflow runs for cannot settle their own request, even as an operator.
Withdrawn requests
A pending request is withdrawn, and its approvers are told, when:
- an operator cancels the run,
- the run fails, for example because the step itself failed through its retries,
- the workflow is deleted,
- the object it is about is erased. This is noticed within about a minute,
- the object leaves the trigger group of a Composer rule run. This is noticed when the run next wakes, at a deadline or after a decision, and the run is cancelled as “transition superseded” before any later step.
A withdrawn request leaves To decide.
Permissions
| Permission | Allows |
|---|---|
workflows.approve | Open the Approvals page and decide the requests you are an approver on. |
workflows.execute | Also see All open and decide any request as an operator. |
workflows.write | Add and edit Approval steps in workflows. |
| Role | workflows.approve |
|---|---|
| Admin | yes |
| Operator | yes |
| Viewer | no |
| User | yes |
The User role is the default role every signed-in person falls back to (see Authentication), so anyone can decide a request that names them. Holding the permission shows nothing by itself: the page lists only the requests the person is an approver on.
The installer’s database step adds what approvals need. A database updated outside the installer needs the database step run before the Admin API starts.
Every decision is recorded in the administrator audit trail with who made it, whether as an approver or as an operator, and the reason. For the permissions of the other kinds on the shared page, see Permissions.
Retention and erasure
Approval requests are removed with their runs by run-history
retention (Retention.WorkflowRunDays, 90 days by default). Rejected
and TimedOut runs age out like Completed ones. See
Retention.
Erasing a person under DSAR and right to erasure
blanks the text of the requests about them: the title reads
[erased], and the subject name and message are removed. Who decided,
and how, stays on the record.
How-to: manager approval before an account is enabled
This example holds a new joiner’s account disabled until their manager approves it. If the manager does not answer in two days, HR is asked as well. A rejection tells HR, and a request nobody answers in five days ends the run.
The example assumes:
- the joiners are people who enter a Composer rule called “New joiners”,
- each person carries a
managerreference to their manager, and the managers can sign in to the portal, - a mesh group called “HR approvers” holds the HR staff,
- an outbound flow exports the mesh attribute
accountEnabledto the account’s enabled flag in the target, such as Entra ID’saccountEnabled, and nothing imports a value back into it.
1. Set the portal link. Open Settings → Email and, under
Links, enter the Portal URL, the plain address of the portal
such as https://identitymesh.example.com, then Save. See Email. Confirm with Send test that email
is working.
2. Create the workflow. Open Configure → Workflows and select New workflow. Set the trigger Kind to Composer rule, pick “New joiners” and the direction In.
3. Add the Approval step. Drag Approval from Control onto
the canvas. Name it “Manager approval” and set its key to
managerApproval. Fill in:
| Field | Value |
|---|---|
| Approvers | Related objects, relationship manager, Objects this one points to |
| Escalate to | Objects matching a query, object type Group, criterion displayName equals HR approvers |
| Approvals needed | 1 |
| Escalate after | 2.00:00:00 |
| Time out after | 5.00:00:00 |
| Title | Enable account for {{displayName}} |
| Message to approvers | {{displayName}} joins {{department}}. Approve to enable their account. |
| When rejected | continue |
| When timed out | end |
4. Insert the outcome fork. Select Insert outcome fork. Two branches appear after the step, Approved? and Rejected?.
- Into the Then arm of Approved?, drag Set attributes and
set
accountEnabledto"true". - Into the Then arm of Rejected?, drag Send email. Send it
to your HR mailbox with the subject
Account not approved for {{displayName}}and a body that includes{{steps.managerApproval.reason}}.
5. Validate and save. Select Validate. The status bar shows ✓ Valid. Save the workflow and enable it.
6. Start a test run. Use Run now against a test joiner whose manager can sign in, and choose In. Open the run from the Runs tab. Confirm that:
- the status is Delayed, with “Delayed until” two days ahead,
- the Waiting for approval panel lists the manager, with no no portal identity mark, and reads “approvals 0 of 1”,
- Escalates and Times out show two and five days ahead.
If the panel lists nobody, or the run ended at once as timed out with “no approver could act”, the test joiner has no manager the portal can recognise. See Troubleshooting.
7. Decide as the manager. Sign in as the manager. Confirm the bell shows “Approval: Enable account for … (needs your approval)” and the email arrived with its link. Open Approvals. The request is on To decide. Select Approve, enter a reason and confirm.
8. Check the outcome. Within seconds the run continues. On the run detail, confirm that:
- the Approval step shows
outcome=approvedand the manager inapprovedBy, - the path runs through the Then arm of Approved?,
- the Set attributes step succeeded and the run is Completed.
Open the joiner’s mesh object. accountEnabled reads true, with the
workflow as its source, and the object is queued for export. After the
next export, the account is enabled in the target.
9. Test a rejection. Start a second test run and reject it with a reason. The run follows Rejected?, HR receives the email with the reason, and the account stays disabled.
Translated MIM approvals
The MIM migration analyzer translates MIM’s own approval activity and the MIM Workflow Activity Library’s Request Approval activity into an Approval step. Approvals from a policy rule’s authorization workflow are placed first in that rule’s workflow, before its other steps.
MIM approves a request before the change is made. A mesh workflow runs after the change is already in the mesh, so the Approval gates the steps that follow it, not the change itself. Every such proposal carries a note saying so. Review what the Approval gates before you enable a translated workflow.
Troubleshooting
| What you see | Cause and fix |
|---|---|
| An approver is marked no portal identity | Their mesh object has no userPrincipalName, mail or sAMAccountName that matches exactly one person. Give the object a value that matches their sign-in, then start a new run. A request that is already open keeps its approver list. |
| The run ended at once as TimedOut, reason “no approver could act” | Nobody in Approvers or Escalate to could act, for example because the person has no manager. Fill the manager attribute, or add an escalation set. |
| The email ends with “Open the IdentityMesh portal and go to Approvals.” | Portal URL is empty. Set it under Settings → Email, Links. The page is Approvals in the sidebar, its own entry directly below Mesh Objects. |
| The email link opens an error page instead of the Approvals page | The Portal URL does not match the address the portal is opened at. Enter the plain address, such as https://identitymesh.example.com, with the right host name, port and path and no #. See Email. |
| The request is on the Approvals page, but no notice is in the bell | The bell matches notices on the sign-in name. Compare the approver’s sign-in with the userPrincipalName on their object. The Approvals page is the reliable list. |
| A decision is refused | It was already settled, another decision landed first, or the object no longer exists. Reload the page. |
| The run stays Delayed after an approval | The run picks a decision up within seconds, and at the latest within about a minute. If it still waits, check that workflows are not paused and that the Operations card shows a recent heartbeat. |