Identify and remediate risks using Microsoft Graph
Article
Microsoft Entra ID Protection provides organizations insight into identity-based risk and different ways to investigate and automatically remediate risk. The Identity Protection APIs used in this tutorial can help you identify risk and configure a workflow to confirm compromise or enable remediation. For more information, see What is risk?
In this tutorial, you learn how to generate a risky sign-in and remediate the risk status of the user with a conditional access policy that requires multi-factor authentication (MFA). An optional section shows you how to block the user from signing in also using a conditional access policy, and dismissing the user risk.
Note
The response objects shown in this tutorial might be shortened for readability.
Prerequisites
To successfully complete this tutorial, make sure that you have the required prerequisites:
You must have a Microsoft Entra ID P1 or P2 license to use the risk detection API.
This tutorial uses the Tor browser to sign in to the Microsoft Entra admin center anonymously. You can use any anonymous browser to accomplish the task. To download the Tor browser, see Download Tor Browser.
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.
Grant yourself the following delegated permissions: IdentityRiskEvent.Read.All, IdentityRiskyUser.ReadWrite.All, Policy.Read.All, Policy.ReadWrite.ConditionalAccess, and User.ReadWrite.All.
Step 1: Create a user account
For this tutorial, you create a user account that is used to test risk detections. In the request body, change contoso.com to the domain name of your tenant. You can find tenant information on the Microsoft Entra overview page.
// 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 = "MyTestUser1",
MailNickname = "MyTestUser1",
UserPrincipalName = "MyTestUser1@contoso.com",
PasswordProfile = new PasswordProfile
{
ForceChangePasswordNextSignIn = true,
Password = "Contoso1234",
},
};
// 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);
// THE CLI IS IN PREVIEW. NON-PRODUCTION USE ONLY
mgc users create --body '{\
"accountEnabled":true,\
"displayName":"MyTestUser1",\
"mailNickname":"MyTestUser1",\
"userPrincipalName":"MyTestUser1@contoso.com",\
"passwordProfile": {\
"forceChangePasswordNextSignIn":true,\
"password":"Contoso1234"\
}\
}\
'
<?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('MyTestUser1');
$requestBody->setMailNickname('MyTestUser1');
$requestBody->setUserPrincipalName('MyTestUser1@contoso.com');
$passwordProfile = new PasswordProfile();
$passwordProfile->setForceChangePasswordNextSignIn(true);
$passwordProfile->setPassword('Contoso1234');
$requestBody->setPasswordProfile($passwordProfile);
$result = $graphServiceClient->users()->post($requestBody)->wait();
One way to trigger a risk detection on a user account is to sign in to the Microsoft Entra admin center anonymously. In this tutorial, the Tor browser is used to sign in anonymously.
Open the browser and enter portal.azure.com for the site address.
Sign in to the portal using the credentials for the MyTestUser1 account that you previously created. You will be asked to change the existing password.
List risk detections
When you signed in to the Microsoft Entra admin center using the anonymous browser, an anonymizedIPAddress risk event was detected. You can use the $filter query parameter to get only the risk detections that are associated with the MyTestUser1 user account.
GET https://graph.microsoft.com/v1.0/identityProtection/riskDetections?$filter=userDisplayName eq 'MyTestUser1'
// 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.IdentityProtection.RiskDetections.GetAsync((requestConfiguration) =>
{
requestConfiguration.QueryParameters.Filter = "userDisplayName eq 'MyTestUser1'";
});
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$requestConfiguration = new RiskDetectionsRequestBuilderGetRequestConfiguration();
$queryParameters = RiskDetectionsRequestBuilderGetRequestConfiguration::createQueryParameters();
$queryParameters->filter = "userDisplayName eq 'MyTestUser1'";
$requestConfiguration->queryParameters = $queryParameters;
$result = $graphServiceClient->identityProtection()->riskDetections()->get($requestConfiguration)->wait();
It may take a few minutes for the event to be returned.
Step 3: Create a conditional access policy
You can leverage conditional access policies in your organization to allow users to self-remediate when risk is detected. Self-remediation enables your users to unblock themselves to access their resources securely after completing the policy prompt. In this step, you create a conditional access policy that requires the user to sign in using MFA if a medium or high risk detection occurs.
Set up multi-factor authentication
When setting up an account for MFA, you can choose from several methods for authenticating the user. Choose the best method for your situation to complete this tutorial.
Complete the MFA setup procedure using the appropriate method for your situation, such as having a text message sent to your phone.
Create the conditional access policy
The conditional access policy provides the ability to set the conditions of the policy to identify sign-in risk levels. Risk levels can be low, medium, high, none. In the response that was returned from listing the risk detections for MyTestUser1, we can see that the risk level is medium. This example shows how to require MFA for MyTestUser1 who was identified as a risky user.
// Code snippets are only available for the latest version. Current version is 5.x
// Dependencies
using Microsoft.Graph.Models;
var requestBody = new ConditionalAccessPolicy
{
DisplayName = "Policy for risky sign-in",
State = ConditionalAccessPolicyState.Enabled,
Conditions = new ConditionalAccessConditionSet
{
SignInRiskLevels = new List<RiskLevel?>
{
RiskLevel.High,
RiskLevel.Medium,
},
Applications = new ConditionalAccessApplications
{
IncludeApplications = new List<string>
{
"All",
},
},
Users = new ConditionalAccessUsers
{
IncludeUsers = new List<string>
{
"4628e7df-dff3-407c-a08f-75f08c0806dc",
},
},
},
GrantControls = new ConditionalAccessGrantControls
{
Operator = "OR",
BuiltInControls = new List<ConditionalAccessGrantControl?>
{
ConditionalAccessGrantControl.Mfa,
},
},
};
// To initialize your graphClient, see https://learn.microsoft.com/en-us/graph/sdks/create-client?from=snippets&tabs=csharp
var result = await graphClient.Identity.ConditionalAccess.Policies.PostAsync(requestBody);
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$requestBody = new ConditionalAccessPolicy();
$requestBody->setDisplayName('Policy for risky sign-in');
$requestBody->setState(new ConditionalAccessPolicyState('enabled'));
$conditions = new ConditionalAccessConditionSet();
$conditions->setSignInRiskLevels([new RiskLevel('high'),new RiskLevel('medium'), ]);
$conditionsApplications = new ConditionalAccessApplications();
$conditionsApplications->setIncludeApplications(['All', ]);
$conditions->setApplications($conditionsApplications);
$conditionsUsers = new ConditionalAccessUsers();
$conditionsUsers->setIncludeUsers(['4628e7df-dff3-407c-a08f-75f08c0806dc', ]);
$conditions->setUsers($conditionsUsers);
$requestBody->setConditions($conditions);
$grantControls = new ConditionalAccessGrantControls();
$grantControls->setOperator('OR');
$grantControls->setBuiltInControls([new ConditionalAccessGrantControl('mfa'), ]);
$requestBody->setGrantControls($grantControls);
$result = $graphServiceClient->identity()->conditionalAccess()->policies()->post($requestBody)->wait();
With this conditional access policy in place, the MyTestUser1 account is now required to use MFA when signing in because the sign-in risk level is medium or high.
Sign in and complete multi-factor authentication
By signing in to the anonymous browser, a risk is detected, but it is remediated by completing MFA.
Open the browser and enter portal.azure.com for the site address.
Sign in to the portal using the credentials for the MyTestUser1 account and complete the MFA process.
List risk detections
Because MFA was completed. Now, when you list risk detections the riskState shows the event as remediated.
GET https://graph.microsoft.com/v1.0/identityProtection/riskDetections?$filter=userDisplayName eq 'MyTestUser1'
// 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.IdentityProtection.RiskDetections.GetAsync((requestConfiguration) =>
{
requestConfiguration.QueryParameters.Filter = "userDisplayName eq 'MyTestUser1'";
});
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$requestConfiguration = new RiskDetectionsRequestBuilderGetRequestConfiguration();
$queryParameters = RiskDetectionsRequestBuilderGetRequestConfiguration::createQueryParameters();
$queryParameters->filter = "userDisplayName eq 'MyTestUser1'";
$requestConfiguration->queryParameters = $queryParameters;
$result = $graphServiceClient->identityProtection()->riskDetections()->get($requestConfiguration)->wait();
Instead of providing the opportunity for the user to self-remediate, you can block the user from signing in. In this step, you create a new conditional access policy that blocks the user from signing in if a medium or high risk detection occurs. The difference in policies is that the builtInControls is set to block.
// Code snippets are only available for the latest version. Current version is 5.x
// Dependencies
using Microsoft.Graph.Models;
var requestBody = new ConditionalAccessPolicy
{
DisplayName = "Policy for risky sign-in block access",
State = ConditionalAccessPolicyState.Enabled,
Conditions = new ConditionalAccessConditionSet
{
SignInRiskLevels = new List<RiskLevel?>
{
RiskLevel.High,
RiskLevel.Medium,
},
Applications = new ConditionalAccessApplications
{
IncludeApplications = new List<string>
{
"All",
},
},
Users = new ConditionalAccessUsers
{
IncludeUsers = new List<string>
{
"4628e7df-dff3-407c-a08f-75f08c0806dc",
},
},
},
GrantControls = new ConditionalAccessGrantControls
{
Operator = "OR",
BuiltInControls = new List<ConditionalAccessGrantControl?>
{
ConditionalAccessGrantControl.Block,
},
},
};
// To initialize your graphClient, see https://learn.microsoft.com/en-us/graph/sdks/create-client?from=snippets&tabs=csharp
var result = await graphClient.Identity.ConditionalAccess.Policies.PostAsync(requestBody);
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$requestBody = new ConditionalAccessPolicy();
$requestBody->setDisplayName('Policy for risky sign-in block access');
$requestBody->setState(new ConditionalAccessPolicyState('enabled'));
$conditions = new ConditionalAccessConditionSet();
$conditions->setSignInRiskLevels([new RiskLevel('high'),new RiskLevel('medium'), ]);
$conditionsApplications = new ConditionalAccessApplications();
$conditionsApplications->setIncludeApplications(['All', ]);
$conditions->setApplications($conditionsApplications);
$conditionsUsers = new ConditionalAccessUsers();
$conditionsUsers->setIncludeUsers(['4628e7df-dff3-407c-a08f-75f08c0806dc', ]);
$conditions->setUsers($conditionsUsers);
$requestBody->setConditions($conditions);
$grantControls = new ConditionalAccessGrantControls();
$grantControls->setOperator('OR');
$grantControls->setBuiltInControls([new ConditionalAccessGrantControl('block'), ]);
$requestBody->setGrantControls($grantControls);
$result = $graphServiceClient->identity()->conditionalAccess()->policies()->post($requestBody)->wait();
POST https://graph.microsoft.com/v1.0/identityProtection/riskyUsers/dismiss
Content-Type: application/json
{
"userIds": [
"4628e7df-dff3-407c-a08f-75f08c0806dc"
]
}
// Code snippets are only available for the latest version. Current version is 5.x
// Dependencies
using Microsoft.Graph.IdentityProtection.RiskyUsers.Dismiss;
var requestBody = new DismissPostRequestBody
{
UserIds = new List<string>
{
"4628e7df-dff3-407c-a08f-75f08c0806dc",
},
};
// To initialize your graphClient, see https://learn.microsoft.com/en-us/graph/sdks/create-client?from=snippets&tabs=csharp
await graphClient.IdentityProtection.RiskyUsers.Dismiss.PostAsync(requestBody);
// THE CLI IS IN PREVIEW. NON-PRODUCTION USE ONLY
mgc identity-protection risky-users dismiss post --body '{\
"userIds": [\
"4628e7df-dff3-407c-a08f-75f08c0806dc"\
]\
}\
'
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$requestBody = new DismissPostRequestBody();
$requestBody->setUserIds(['4628e7df-dff3-407c-a08f-75f08c0806dc', ]);
$graphServiceClient->identityProtection()->riskyUsers()->dismiss()->post($requestBody)->wait();
# THE PYTHON SDK IS IN PREVIEW. FOR NON-PRODUCTION USE ONLY
graph_client = GraphServiceClient(credentials, scopes)
request_body = DismissPostRequestBody(
user_ids = [
"4628e7df-dff3-407c-a08f-75f08c0806dc",
],
)
await graph_client.identity_protection.risky_users.dismiss.post(request_body)
After dismissing the risk user, you can see in the response when listing risky users that the MyTestUser1 user account now has a risk level of none and a riskState of dismissed.
GET https://graph.microsoft.com/v1.0/identityProtection/riskyUsers?$filter=userDisplayName eq 'MyTestUser1'
// 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.IdentityProtection.RiskyUsers.GetAsync((requestConfiguration) =>
{
requestConfiguration.QueryParameters.Filter = "userDisplayName eq 'MyTestUser1'";
});
<?php
// THIS SNIPPET IS A PREVIEW VERSION OF THE SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);
$requestConfiguration = new RiskyUsersRequestBuilderGetRequestConfiguration();
$queryParameters = RiskyUsersRequestBuilderGetRequestConfiguration::createQueryParameters();
$queryParameters->filter = "userDisplayName eq 'MyTestUser1'";
$requestConfiguration->queryParameters = $queryParameters;
$result = $graphServiceClient->identityProtection()->riskyUsers()->get($requestConfiguration)->wait();
// 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()
// 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()