Assign Microsoft Entra roles through Privileged Identity Management (PIM) APIs in Microsoft Graph
Article
Microsoft Graph PIM API enables organizations to manage privileged access to resources in Microsoft Entra ID. It also helps to manage the risks of privileged access by limiting when access is active, managing the scope of access, and providing an auditable log of privileged access.
In this tutorial, a fictitious company called Contoso Limited wishes to have its IT Helpdesk manage the lifecycle of employees' access. The company has identified the Microsoft Entra User Administrator role as the appropriate privileged role required by IT Helpdesk, and will use the PIM API to assign the role.
You'll create a role-assignable security group for IT Helpdesk and using the PIM API, assign the security group eligibility to the User Administrator role. By assigning the eligible role to a security group, Contoso has a more efficient way to manage administrator access to resources such as Microsoft Entra roles. For example:
Removing existing or adding more group members also removes administrators.
Adding more roles to the group members instead of assigning roles to individual users.
Assigning eligibility instead of a persistently active User Administrator privilege allows the company to enforce just-in-time access, which grants temporary permissions to carry out the privileged tasks. After defining the role eligibility, the eligible group member then activates their assignment for a temporary period. All records of role activations will be auditable by the company.
Note
The response objects shown in this tutorial might be shortened for readability.
Prerequisites
To complete this tutorial, you need the following resources and privileges:
A working Microsoft Entra tenant with a Microsoft Entra ID P2 or EMS E5 license enabled.
Sign in to an API client such as Graph Explorer, Postman, or create your own client app to call Microsoft Graph. To call Microsoft Graph APIs in this tutorial, you need to use an account with the Global Administrator role.
[Optional] Start a new session in another browser. You'll sign in later in this tutorial.
Grant yourself the following delegated permissions: User.ReadWrite.All, Group.ReadWrite.All, Directory.Read.All, RoleEligibilitySchedule.ReadWrite.Directory, and RoleAssignmentSchedule.ReadWrite.Directory, and RoleManagement.ReadWrite.Directory.
Authenticator app installed on your phone to register a user for multifactor authentication (MFA).
Step 1: Create a test user
Create a user who must reset their password at first sign in. From this step, record the value of the new user's id for use in the next step. After creating the user, visit the Microsoft Entra admin center and enable multifactor authentication (MFA) for the user. For more information about enabling MFA, see the See also section.
// Code snippets are only available for the latest version. Current version is 5.x
// Dependencies
using Microsoft.Graph.Models;
var requestBody = new User
{
AccountEnabled = true,
DisplayName = "Aline Dupuy",
MailNickname = "AlineD",
UserPrincipalName = "AlineD@Contoso.com",
PasswordProfile = new PasswordProfile
{
ForceChangePasswordNextSignIn = true,
Password = "xWwvJ]6NMw+bWH-d",
},
};
// To initialize your graphClient, see https://learn.microsoft.com/en-us/graph/sdks/create-client?from=snippets&tabs=csharp
var result = await graphClient.Users.PostAsync(requestBody);
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$requestBody = new User();
$requestBody->setAccountEnabled(true);
$requestBody->setDisplayName('Aline Dupuy');
$requestBody->setMailNickname('AlineD');
$requestBody->setUserPrincipalName('AlineD@Contoso.com');
$passwordProfile = new PasswordProfile();
$passwordProfile->setForceChangePasswordNextSignIn(true);
$passwordProfile->setPassword('xWwvJ]6NMw+bWH-d');
$requestBody->setPasswordProfile($passwordProfile);
$result = $graphServiceClient->users()->post($requestBody)->wait();
Step 2: Create a security group that can be assigned a Microsoft Entra role
Create a group that's assignable to a Microsoft Entra role. Assign yourself as the group owner and both you and Aline (the user created in Step 1) as members.
Request: Create a role-assignable group
Replace 1ed8ac56-4827-4733-8f80-86adc2e67db5 with your ID and 7146daa8-1b4b-4a66-b2f7-cf593d03c8d2 with the value of Aline's ID.
// Code snippets are only available for the latest version. Current version is 5.x
// Dependencies
using Microsoft.Graph.Models;
var requestBody = new Group
{
Description = "IT Helpdesk to support Contoso employees",
DisplayName = "IT Helpdesk (User)",
MailEnabled = false,
MailNickname = "userHelpdesk",
SecurityEnabled = true,
IsAssignableToRole = true,
AdditionalData = new Dictionary<string, object>
{
{
"owners@odata.bind" , new List<string>
{
"https://graph.microsoft.com/v1.0/users/1ed8ac56-4827-4733-8f80-86adc2e67db5",
}
},
{
"members@odata.bind" , new List<string>
{
"https://graph.microsoft.com/v1.0/users/1ed8ac56-4827-4733-8f80-86adc2e67db5",
"https://graph.microsoft.com/v1.0/users/7146daa8-1b4b-4a66-b2f7-cf593d03c8d2",
}
},
},
};
// To initialize your graphClient, see https://learn.microsoft.com/en-us/graph/sdks/create-client?from=snippets&tabs=csharp
var result = await graphClient.Groups.PostAsync(requestBody);
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$requestBody = new Group();
$requestBody->setDescription('IT Helpdesk to support Contoso employees');
$requestBody->setDisplayName('IT Helpdesk (User)');
$requestBody->setMailEnabled(false);
$requestBody->setMailNickname('userHelpdesk');
$requestBody->setSecurityEnabled(true);
$requestBody->setIsAssignableToRole(true);
$additionalData = [
'owners@odata.bind' => [
'https://graph.microsoft.com/v1.0/users/1ed8ac56-4827-4733-8f80-86adc2e67db5', ],
'members@odata.bind' => [
'https://graph.microsoft.com/v1.0/users/1ed8ac56-4827-4733-8f80-86adc2e67db5', 'https://graph.microsoft.com/v1.0/users/7146daa8-1b4b-4a66-b2f7-cf593d03c8d2', ],
];
$requestBody->setAdditionalData($additionalData);
$result = $graphServiceClient->groups()->post($requestBody)->wait();
Step 3: Create a unifiedRoleEligibilityScheduleRequest
Now that you have a security group, assign it as eligible for the User Administrator role. In this step:
Create a unifiedRoleEligibilityScheduleRequest object that identifies the group IT Helpdesk (User) as eligible for the User Administrator role for one year. Microsoft Entra ID extends this eligible assignment to the group members, that is, you and Aline.
Scope the eligible assignment to your entire tenant. This allows the user admin to use their privilege against all users in your tenant, except higher privileged users such as the Global Administrator.
Request
Replace e77cbb23-0ff2-4e18-819c-690f58269752 with the value of the id of the IT Helpdesk (User) security group. This principalId identifies the assignee of eligibility to the User Administrator role. The roleDefinitionId fe930be7-5e62-47db-91af-98c3a49a38b1 is the global template identifier for the Microsoft Entra User Administrator role.
POST https://graph.microsoft.com/v1.0/roleManagement/directory/roleEligibilityScheduleRequests
Content-type: application/json
{
"action": "AdminAssign",
"justification": "Assign User Admin eligibility to IT Helpdesk (User) group",
"roleDefinitionId": "fe930be7-5e62-47db-91af-98c3a49a38b1",
"directoryScopeId": "/",
"principalId": "e77cbb23-0ff2-4e18-819c-690f58269752",
"scheduleInfo": {
"startDateTime": "2021-07-01T00:00:00Z",
"expiration": {
"endDateTime": "2022-06-30T00:00:00Z",
"type": "AfterDateTime"
}
}
}
// Code snippets are only available for the latest version. Current version is 5.x
// Dependencies
using Microsoft.Graph.Models;
var requestBody = new UnifiedRoleEligibilityScheduleRequest
{
Action = UnifiedRoleScheduleRequestActions.AdminAssign,
Justification = "Assign User Admin eligibility to IT Helpdesk (User) group",
RoleDefinitionId = "fe930be7-5e62-47db-91af-98c3a49a38b1",
DirectoryScopeId = "/",
PrincipalId = "e77cbb23-0ff2-4e18-819c-690f58269752",
ScheduleInfo = new RequestSchedule
{
StartDateTime = DateTimeOffset.Parse("2021-07-01T00:00:00Z"),
Expiration = new ExpirationPattern
{
EndDateTime = DateTimeOffset.Parse("2022-06-30T00:00:00Z"),
Type = ExpirationPatternType.AfterDateTime,
},
},
};
// To initialize your graphClient, see https://learn.microsoft.com/en-us/graph/sdks/create-client?from=snippets&tabs=csharp
var result = await graphClient.RoleManagement.Directory.RoleEligibilityScheduleRequests.PostAsync(requestBody);
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$requestBody = new UnifiedRoleEligibilityScheduleRequest();
$requestBody->setAction(new UnifiedRoleScheduleRequestActions('adminAssign'));
$requestBody->setJustification('Assign User Admin eligibility to IT Helpdesk (User) group');
$requestBody->setRoleDefinitionId('fe930be7-5e62-47db-91af-98c3a49a38b1');
$requestBody->setDirectoryScopeId('/');
$requestBody->setPrincipalId('e77cbb23-0ff2-4e18-819c-690f58269752');
$scheduleInfo = new RequestSchedule();
$scheduleInfo->setStartDateTime(new \DateTime('2021-07-01T00:00:00Z'));
$scheduleInfoExpiration = new ExpirationPattern();
$scheduleInfoExpiration->setEndDateTime(new \DateTime('2022-06-30T00:00:00Z'));
$scheduleInfoExpiration->setType(new ExpirationPatternType('afterDateTime'));
$scheduleInfo->setExpiration($scheduleInfoExpiration);
$requestBody->setScheduleInfo($scheduleInfo);
$result = $graphServiceClient->roleManagement()->directory()->roleEligibilityScheduleRequests()->post($requestBody)->wait();
Step 4: Confirm the user's current role assignments
While the group members are now eligible for the User Administrator role, they're still unable to use the role. This is because they're yet to activate their eligibility. You can confirm by checking the user's current role assignments.
Request
In the following request, replace 7146daa8-1b4b-4a66-b2f7-cf593d03c8d2 with the value of Aline's id.
GET https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments?$filter=principalId eq '7146daa8-1b4b-4a66-b2f7-cf593d03c8d2'
// Code snippets are only available for the latest version. Current version is 5.x
// To initialize your graphClient, see https://learn.microsoft.com/en-us/graph/sdks/create-client?from=snippets&tabs=csharp
var result = await graphClient.RoleManagement.Directory.RoleAssignments.GetAsync((requestConfiguration) =>
{
requestConfiguration.QueryParameters.Filter = "principalId eq '7146daa8-1b4b-4a66-b2f7-cf593d03c8d2'";
});
// THE CLI IS IN PREVIEW. NON-PRODUCTION USE ONLY
mgc role-management directory role-assignments list --filter "principalId eq '7146daa8-1b4b-4a66-b2f7-cf593d03c8d2'"
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$requestConfiguration = new RoleAssignmentsRequestBuilderGetRequestConfiguration();
$queryParameters = RoleAssignmentsRequestBuilderGetRequestConfiguration::createQueryParameters();
$queryParameters->filter = "principalId eq '7146daa8-1b4b-4a66-b2f7-cf593d03c8d2'";
$requestConfiguration->queryParameters = $queryParameters;
$result = $graphServiceClient->roleManagement()->directory()->roleAssignments()->get($requestConfiguration)->wait();
The empty response object shows that Aline has no existing Microsoft Entra roles in Contoso. Aline will now activate their eligible User Administrator role for a limited time.
Step 5: User self-activates their eligible assignment
An incident ticket CONTOSO: Security-012345 has been raised in Contoso's incident management system and the company requires that all employee's refresh tokens be invalidated. As a member of IT Helpdesk, Aline is responsible for fulfilling this task.
First, start the Authenticator app on your phone and open Aline Dupuy's account.
Sign in to Graph Explorer as Aline. You may use the another browser for this step. By doing so, you won't interrupt your current session as a user in the Global Administrator role. Alternatively, you can interrupt your current session by signing out of Graph Explorer and signing back in as Aline.
Signed in as Aline, you'll first change your password because this was specified during account creation. Then, because the administrator configured your account for MFA, you'll be prompted to set up your account in the Authenticator app and be challenged for MFA sign-in. This is because PIM requires that MFA for all active role assignments.
After signing in, activate your User Administrator role for five hours.
Request
To activate a role, call the roleAssignmentScheduleRequests endpoint. In this request, the UserActivate action allows you to activate your eligible assignment, in this case for five hours.
For principalId, supply the value of your (Aline's) id.
The roleDefinitionId is the id of the role you're eligible for, in this case, the User Administrator role.
Enter the details of the ticket system that provides an auditable justification for activating the request.
// Code snippets are only available for the latest version. Current version is 5.x
// Dependencies
using Microsoft.Graph.Models;
var requestBody = new UnifiedRoleAssignmentScheduleRequest
{
Action = UnifiedRoleScheduleRequestActions.SelfActivate,
PrincipalId = "7146daa8-1b4b-4a66-b2f7-cf593d03c8d2",
RoleDefinitionId = "fe930be7-5e62-47db-91af-98c3a49a38b1",
DirectoryScopeId = "/",
Justification = "Need to invalidate all app refresh tokens for Contoso users.",
ScheduleInfo = new RequestSchedule
{
StartDateTime = DateTimeOffset.Parse("2021-09-04T15:13:00.000Z"),
Expiration = new ExpirationPattern
{
Type = ExpirationPatternType.AfterDuration,
Duration = TimeSpan.Parse("PT5H"),
},
},
TicketInfo = new TicketInfo
{
TicketNumber = "CONTOSO:Security-012345",
TicketSystem = "Contoso ICM",
},
};
// To initialize your graphClient, see https://learn.microsoft.com/en-us/graph/sdks/create-client?from=snippets&tabs=csharp
var result = await graphClient.RoleManagement.Directory.RoleAssignmentScheduleRequests.PostAsync(requestBody);
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$requestBody = new UnifiedRoleAssignmentScheduleRequest();
$requestBody->setAction(new UnifiedRoleScheduleRequestActions('selfActivate'));
$requestBody->setPrincipalId('7146daa8-1b4b-4a66-b2f7-cf593d03c8d2');
$requestBody->setRoleDefinitionId('fe930be7-5e62-47db-91af-98c3a49a38b1');
$requestBody->setDirectoryScopeId('/');
$requestBody->setJustification('Need to invalidate all app refresh tokens for Contoso users.');
$scheduleInfo = new RequestSchedule();
$scheduleInfo->setStartDateTime(new \DateTime('2021-09-04T15:13:00.000Z'));
$scheduleInfoExpiration = new ExpirationPattern();
$scheduleInfoExpiration->setType(new ExpirationPatternType('afterDuration'));
$scheduleInfoExpiration->setDuration(new \DateInterval('PT5H'));
$scheduleInfo->setExpiration($scheduleInfoExpiration);
$requestBody->setScheduleInfo($scheduleInfo);
$ticketInfo = new TicketInfo();
$ticketInfo->setTicketNumber('CONTOSO:Security-012345');
$ticketInfo->setTicketSystem('Contoso ICM');
$requestBody->setTicketInfo($ticketInfo);
$result = $graphServiceClient->roleManagement()->directory()->roleAssignmentScheduleRequests()->post($requestBody)->wait();
You may confirm your assignment by running GET https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignmentScheduleRequests/filterByCurrentUser(on='principal'). The response object returns your newly activated role assignment with its status set to Granted. With your new privilege, carry out any allowed actions within five hours your assignment is active for. This includes invalidating all employees' refresh tokens. After five hours, the active assignment expires but through your membership in the IT Support (Users) group, you still remain eligible for the User Administrator role.
Back in the global administrator session, you have received notifications of both the eligible assignment and the role activation. This allows the global administrator to be aware of all elevations to administrator privileges across your organization.
Step 6: Clean up resources
Sign in as the Global Administrator and delete the following resources that you created for this tutorial: the role eligibility request, IT Support (Users) group, and the test user (Aline Dupuy).
Revoke the role eligibility request
Request
Replace e77cbb23-0ff2-4e18-819c-690f58269752 with the id of IT Support (Users) group.
// Code snippets are only available for the latest version. Current version is 5.x
// Dependencies
using Microsoft.Graph.Models;
var requestBody = new UnifiedRoleEligibilityScheduleRequest
{
Action = UnifiedRoleScheduleRequestActions.AdminRemove,
PrincipalId = "e77cbb23-0ff2-4e18-819c-690f58269752",
RoleDefinitionId = "fe930be7-5e62-47db-91af-98c3a49a38b1",
DirectoryScopeId = "/",
};
// To initialize your graphClient, see https://learn.microsoft.com/en-us/graph/sdks/create-client?from=snippets&tabs=csharp
var result = await graphClient.RoleManagement.Directory.RoleEligibilityScheduleRequests.PostAsync(requestBody);
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$requestBody = new UnifiedRoleEligibilityScheduleRequest();
$requestBody->setAction(new UnifiedRoleScheduleRequestActions('adminRemove'));
$requestBody->setPrincipalId('e77cbb23-0ff2-4e18-819c-690f58269752');
$requestBody->setRoleDefinitionId('fe930be7-5e62-47db-91af-98c3a49a38b1');
$requestBody->setDirectoryScopeId('/');
$result = $graphServiceClient->roleManagement()->directory()->roleEligibilityScheduleRequests()->post($requestBody)->wait();
# THE PYTHON SDK IS IN PREVIEW. FOR NON-PRODUCTION USE ONLY
graph_client = GraphServiceClient(credentials, scopes)
request_body = UnifiedRoleEligibilityScheduleRequest(
action = UnifiedRoleScheduleRequestActions.AdminRemove,
principal_id = "e77cbb23-0ff2-4e18-819c-690f58269752",
role_definition_id = "fe930be7-5e62-47db-91af-98c3a49a38b1",
directory_scope_id = "/",
)
result = await graph_client.role_management.directory.role_eligibility_schedule_requests.post(request_body)
// Code snippets are only available for the latest version. Current version is 5.x
// To initialize your graphClient, see https://learn.microsoft.com/en-us/graph/sdks/create-client?from=snippets&tabs=csharp
await graphClient.Groups["{group-id}"].DeleteAsync();
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$graphServiceClient->groups()->byGroupId('group-id')->delete()->wait();
# THE PYTHON SDK IS IN PREVIEW. FOR NON-PRODUCTION USE ONLY
graph_client = GraphServiceClient(credentials, scopes)
await graph_client.groups.by_group_id('group-id').delete()
// Code snippets are only available for the latest version. Current version is 5.x
// To initialize your graphClient, see https://learn.microsoft.com/en-us/graph/sdks/create-client?from=snippets&tabs=csharp
await graphClient.Users["{user-id}"].DeleteAsync();
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$graphServiceClient->users()->byUserId('user-id')->delete()->wait();
# THE PYTHON SDK IS IN PREVIEW. FOR NON-PRODUCTION USE ONLY
graph_client = GraphServiceClient(credentials, scopes)
await graph_client.users.by_user_id('user-id').delete()