Identify and remediate risks using Microsoft Graph
Article
21 minutes to read
Azure AD Identity 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 an Azure AD Premium P1 or P2 license to use the risk detection API.
This tutorial uses the Tor browser to sign in to the Azure portal 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 Azure Active Directory overview page.
<?php
// THIS SNIPPET IS A PREVIEW FOR THE KIOTA BASED SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($requestAdapter);
$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);
$requestResult = $graphServiceClient->users()->post($requestBody);
One way to trigger a risk detection on a user account is to sign in to the Azure portal 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 Azure portal 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'
var graphClient = new GraphServiceClient(requestAdapter);
var result = await graphClient.IdentityProtection.RiskDetections.GetAsync((requestConfiguration) =>
{
requestConfiguration.QueryParameters.Filter = "userDisplayName eq 'MyTestUser1'";
});
<?php
// THIS SNIPPET IS A PREVIEW FOR THE KIOTA BASED SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($requestAdapter);
$requestConfiguration = new RiskDetectionsRequestBuilderGetRequestConfiguration();
$queryParameters = new RiskDetectionsRequestBuilderGetQueryParameters();
$queryParameters->filter = "userDisplayName eq 'MyTestUser1'";
$requestConfiguration->queryParameters = $queryParameters;
$requestResult = $graphServiceClient->identityProtection()->riskDetections()->get($requestConfiguration);
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.
<?php
// THIS SNIPPET IS A PREVIEW FOR THE KIOTA BASED SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($requestAdapter);
$requestBody = new ConditionalAccessPolicy();
$requestBody->setDisplayName('Policy for risky sign-in');
$requestBody->setState(new ConditionalAccessPolicyState('enabled'));
$conditions = new ConditionalAccessConditionSet();
$conditions->setSignInRiskLevels([$conditions->setRiskLevel(new RiskLevel('high'));
$conditions->setRiskLevel(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([$grantControls->setConditionalAccessGrantControl(new ConditionalAccessGrantControl('mfa'));
]);
$requestBody->setGrantControls($grantControls);
$requestResult = $graphServiceClient->identity()->conditionalAccess()->policies()->post($requestBody);
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'
var graphClient = new GraphServiceClient(requestAdapter);
var result = await graphClient.IdentityProtection.RiskDetections.GetAsync((requestConfiguration) =>
{
requestConfiguration.QueryParameters.Filter = "userDisplayName eq 'MyTestUser1'";
});
<?php
// THIS SNIPPET IS A PREVIEW FOR THE KIOTA BASED SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($requestAdapter);
$requestConfiguration = new RiskDetectionsRequestBuilderGetRequestConfiguration();
$queryParameters = new RiskDetectionsRequestBuilderGetQueryParameters();
$queryParameters->filter = "userDisplayName eq 'MyTestUser1'";
$requestConfiguration->queryParameters = $queryParameters;
$requestResult = $graphServiceClient->identityProtection()->riskDetections()->get($requestConfiguration);
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.
<?php
// THIS SNIPPET IS A PREVIEW FOR THE KIOTA BASED SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($requestAdapter);
$requestBody = new ConditionalAccessPolicy();
$requestBody->setDisplayName('Policy for risky sign-in');
$requestBody->setState(new ConditionalAccessPolicyState('enabled'));
$conditions = new ConditionalAccessConditionSet();
$conditions->setSignInRiskLevels([$conditions->setRiskLevel(new RiskLevel('high'));
$conditions->setRiskLevel(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([$grantControls->setConditionalAccessGrantControl(new ConditionalAccessGrantControl('mfa'));
]);
$requestBody->setGrantControls($grantControls);
$requestResult = $graphServiceClient->identity()->conditionalAccess()->policies()->post($requestBody);
POST https://graph.microsoft.com/v1.0/identityProtection/riskyUsers/dismiss
Content-Type: application/json
{
"userIds": [
"4628e7df-dff3-407c-a08f-75f08c0806dc"
]
}
var graphClient = new GraphServiceClient(requestAdapter);
var requestBody = new Microsoft.Graph.IdentityProtection.RiskyUsers.Dismiss.DismissPostRequestBody
{
UserIds = new List<string>
{
"4628e7df-dff3-407c-a08f-75f08c0806dc",
},
};
await graphClient.IdentityProtection.RiskyUsers.Dismiss.PostAsync(requestBody);
<?php
// THIS SNIPPET IS A PREVIEW FOR THE KIOTA BASED SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($requestAdapter);
$requestBody = new DismissPostRequestBody();
$requestBody->setUserIds(['4628e7df-dff3-407c-a08f-75f08c0806dc', ]);
$graphServiceClient->identityProtection()->riskyUsers()->dismiss()->post($requestBody);
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'
var graphClient = new GraphServiceClient(requestAdapter);
var result = await graphClient.IdentityProtection.RiskyUsers.GetAsync((requestConfiguration) =>
{
requestConfiguration.QueryParameters.Filter = "userDisplayName eq 'MyTestUser1'";
});
<?php
// THIS SNIPPET IS A PREVIEW FOR THE KIOTA BASED SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($requestAdapter);
$requestConfiguration = new RiskyUsersRequestBuilderGetRequestConfiguration();
$queryParameters = new RiskyUsersRequestBuilderGetQueryParameters();
$queryParameters->filter = "userDisplayName eq 'MyTestUser1'";
$requestConfiguration->queryParameters = $queryParameters;
$requestResult = $graphServiceClient->identityProtection()->riskyUsers()->get($requestConfiguration);
//THE GO SDK IS IN PREVIEW. NON-PRODUCTION USE ONLY
graphClient := msgraphsdk.NewGraphServiceClientWithCredentials(cred, scopes)
graphClient.UsersById("user-id").Delete(context.Background(), nil)
<?php
// THIS SNIPPET IS A PREVIEW FOR THE KIOTA BASED SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($requestAdapter);
$graphServiceClient->usersById('user-id')->delete();
//THE GO SDK IS IN PREVIEW. NON-PRODUCTION USE ONLY
graphClient := msgraphsdk.NewGraphServiceClientWithCredentials(cred, scopes)
graphClient.GroupsById("group-id").Delete(context.Background(), nil)
<?php
// THIS SNIPPET IS A PREVIEW FOR THE KIOTA BASED SDK. NON-PRODUCTION USE ONLY
$graphServiceClient = new GraphServiceClient($requestAdapter);
$graphServiceClient->groupsById('group-id')->delete();