From a security perspective, organizations want to minimize the number of people who have access to secure information or resources. With Privileged Identity Management (PIM), you can give users just-in-time privileged access to Azure and Azure AD resources. Furthermore, you can assign time-bound access to resources using start and end dates and enforce an approval to activate privileged roles.
At the time of writing, PIM works very well with the Azure Portal. Unfortunately, there is no documentation / complete sample on how to approve or deny PIM requests programmatically.
This article will show you how to programmatically retrieve, deny, and approve PIM requests using the Microsoft Graph API.
Prerequisites
Authentication
Let’s start with the basics. Before we can invoke the PIM Graph API for Azure Active Directory roles, we need to get an access token from the Microsoft identity platform including the following scopes/permissions:
Permission | Description | Admin Consent |
---|---|---|
PrivilegedAccess.Read.AzureAD | Allows the app to have read access to Privileged Identity Management APIs for Azure AD. | Yes |
PrivilegedAccess.ReadWrite.AzureAD | Allows the app to have read and write access to Privileged Identity Management APIs for Azure AD. | Yes |
RoleAssignmentSchedule.ReadWrite.Directory | Allows the app to read and manage the active role-based access control (RBAC) assignments for your company’s directory, on behalf of the signed-in user. This includes managing active directory role membership, and reading directory role templates, directory roles, and active memberships. | Yes |
The easiest way to get the token is through the Microsoft Graph PowerShell SDK. If you don’t have the SDK yet, you can install it by running the following command in a PowerShell console:
Install-Module Microsoft.Graph
Once the module is installed, we can use the cmdlet to sign in. Here we will also pass a list of delegated scopes we need access to:
$scopes = @( "PrivilegedAccess.Read.AzureAD", "RoleAssignmentSchedule.ReadWrite.Directory", "PrivilegedAccess.ReadWrite.AzureAD" ) Connect-MgGraph -Scopes $scopes
At this point, you are prompted to enter your credentials to authenticate with Azure AD. The Microsoft Identity Platform v2.0 endpoint also ensures that we have agreed to the permissions specified in the -Scopes
parameter.
If we have not yet agreed to any of these permissions, and if an administrator has not previously agreed on behalf of all users in the organization, we will be prompted to consent to the required permissions:
After we accept the permissions request, the consent process will create a service principal (if not present) within our tenant including access to the requested scope:
Using the Privileged Identity Management API
After we have successfully logged in, we can use the Microsoft Graph PowerShell module to execute REST queries using the Invoke-GraphRequest
cmdlet. This function is essentially a Invoke-WebRequest
wrapper that handles the Access Token lifecycle and injects the Authorization header for us.
Retrieve a list of pending role assignment requests
The endpoint we are looking for is called List unifiedRoleAssignmentScheduleRequests. Here is how the request looks like:
GET https://graph.microsoft.com/beta/roleManagement/directory/roleAssignmentScheduleRequests
The method would return every current and previous role assignment request, including requests that have already been provisioned, revoked, timed out, denied, or canceled. Fortunately, the endpoint supports some OData query parameters so we can pre-filter the collection to only contain pending approvals:
GET https://graph.microsoft.com/beta/roleManagement/directory/roleAssignmentScheduleRequests?$filter=status eq 'PendingApproval'
Here is how the request looks like using the Invoke-GraphRequest
cmdlet. Note that we have to escape the single quotes in PowerShell:
Invoke-GraphRequest ` -Method GET ` -Uri '/beta/roleManagement/directory/roleAssignmentScheduleRequests?$filter=(status eq ''PendingApproval'')'
Following is the corresponding answer for a single pending role assignment in JSON (pipe to ConvertTo-Json
cmdlet)
{ "value": [ { "justification": "need to create an app registration for project x", "targetScheduleId": "929cd687-a296-4f77-bdbc-b7cc22d3517e", "status": "PendingApproval", "isValidationOnly": false, "roleDefinitionId": "9b895d92-2cd3-44c7-9d02-a6ac2d5ea5c3", "completedDateTime": "2021-07-19T08:49:49.603Z", "createdDateTime": "2021-07-19T08:49:49.78Z", "appScopeId": null, "scheduleInfo": { "startDateTime": null, "expiration": { "type": "afterDuration", "endDateTime": null, "duration": "P1D" }, "recurrence": null }, "customData": null, "directoryScopeId": "/", "principalId": "fb0cf6ad-f076-419a-909e-d8751e8ae93c", "id": "929cd687-a296-4f77-bdbc-b7cc22d3517e", "approvalId": "929cd687-a296-4f77-bdbc-b7cc22d3517e", "action": "SelfActivate", "ticketInfo": { "ticketSystem": "", "ticketNumber": "" }, "createdBy": { "user": { "id": "fb0cf6ad-f076-419a-909e-d8751e8ae93c", "displayName": null }, "application": null, "device": null } } ], "@odata.context": "https://graph.microsoft.com/beta/$metadata#roleManagement/directory/roleAssignmentScheduleRequests" }
The endpoint returns basically the list of requests for role activations that we have in the Azure Portal:
Approve pending role assignments
Two steps are required to approve a pending role assignment. First, we need to retrieve the corresponding roleAssignmentApproval
object using the following request:
GET https://graph.microsoft.com/beta/roleManagement/directory/roleAssignmentApprovals/<approvalId>
In the above example, the approval Id was 929cd687-a296-4f77-bdbc-b7cc22d3517e
. Again, here is the request in PowerShell:
Invoke-GraphRequest ` -Method GET ` -Uri '/beta/roleManagement/directory/roleAssignmentApprovals/929cd687-a296-4f77-bdbc-b7cc22d3517e'
The response may contain a list of steps (in my case it is only one). We need to select the entry where the status
is InProgress
:
{ "@odata.context": "https://graph.microsoft.com/beta/$metadata#roleManagement/directory/roleAssignmentApprovals/$entity", "steps": [ { "reviewedDateTime": null, "status": "InProgress", "reviewResult": "NotReviewed", "displayName": null, "reviewedBy": null, "assignedToMe": true, "id": "4cd94d49-9ed2-4eee-896f-cffd2260a820", "justification": "" } ], "steps@odata.context": "https://graph.microsoft.com/beta/$metadata#roleManagement/directory/roleAssignmentApprovals('929cd687-a296-4f77-bdbc-b7cc22d3517e')/steps", "id": "929cd687-a296-4f77-bdbc-b7cc22d3517e" }
Finally, we can approve the request by executing a PATCH request on that step:
PATCH https://graph.microsoft.com/beta/roleManagement/directory/roleAssignmentApprovals/<approvalId>/steps/<stepId> { "reviewResult": "Approve", "justification": "<REASON>" }
For our specific role assignment request, the step id is 4cd94d49-9ed2-4eee-896f-cffd2260a820
and the corresponding PowerShell call looks like this:
Invoke-GraphRequest ` -Method PATCH ` -Uri 'https://graph.microsoft.com/beta/roleManagement/directory/roleAssignmentApprovals/929cd687-a296-4f77-bdbc-b7cc22d3517e/steps/4cd94d49-9ed2-4eee-896f-cffd2260a820' ` -Body $body
After executing the command, we can verify that the approval was successful in the Azure Active Directory audit history:
Wrap it up
A complete PowerShell example that approves the first pending assignment schedule request is shown below.
$scopes = @( "PrivilegedAccess.Read.AzureAD", "RoleAssignmentSchedule.ReadWrite.Directory", "PrivilegedAccess.ReadWrite.AzureAD" ) Connect-MgGraph -Scopes $scopes [array]$pendingApprovals = Invoke-GraphRequest ` -Method GET ` -Uri '/beta/roleManagement/directory/roleAssignmentScheduleRequests?$filter=(status eq ''PendingApproval'')' | Select-Object -ExpandProperty value $approvalSteps = Invoke-GraphRequest ` -Method GET ` -Uri ('/beta/roleManagement/directory/roleAssignmentApprovals/{0}' -f $pendingApprovals[0].approvalId) | Select-Object -ExpandProperty steps | Where-Object status -eq InProgress $body = @{ reviewResult = 'Approve' justification = 'Seems legit' } Invoke-GraphRequest ` -Method PATCH ` -Uri ('https://graph.microsoft.com/beta/roleManagement/directory/roleAssignmentApprovals/{0}/steps/{1}' -f $pendingApprovals[0].approvalId, $approvalSteps.id) ` -Body $body
Hi Martin,
I’ve just read your article and I have a question to you.
But first of all, thanks for the article, looks great 🙂
I am working on similar things and I’ve tested your approach. It is working how it should work!
In my project I am trying to do this for Azure Resources and got to point where I struggle a bit with the Microsoft APIs. I get every information out from the API but I am not able to approve/deny a request. My attempts:
– Graph API (https://docs.microsoft.com/en-us/graph/api/governanceroleassignmentrequest-update?view=graph-rest-beta&tabs=http)
– Management API (https://docs.microsoft.com/en-us/rest/api/authorization/role-assignment-approval-step/patch)
– I also tried to get access to the access reviews
– …
The Graph API told me that there is no request for this id. The problem here is the status I guess, the API wants to have “PendingAdminDecision” and we have “PendingApproval”.
Management API gives me back a 500 – internal server error.
Have you ever tested this or have any idea?
Best regards
Michael
Hi Michael,
Thanks for your feedback! Unfortunately, I have not yet worked with PIM in the context of Azure Resources so I cannot tell you what is the right endpoint for the Approvement. I would suggest opening a Support Case :-/
BR,
Martin