I'm trying to automate purges.
I've created an application, given it the Application right eDiscovery.ReadWrite.All
, and given consent.
I've created an oauth2 access token, and verified that the scope is present in the JWT token.
However, when I call .0/security/cases/ediscoveryCases with the Authorization: Bearer {accesstoken}
I get a 401 error.
According to the documentation that I found (.0&tabs=http) the application right should be enough to access this endpoint.
Am I missing something? Do I need to give my app additional rights? Or does (despite what the documentation says) application access not work, and do I need to use delegated permissions?
Code (Python):
Get access token using tenant, app and secret (obviously not given here), and create the header:
body = { "client_id": app, "scope": "/.default", "grant_type": "client_credentials", "client_secret": secret }
token = requests.post("/{}/oauth2/v2.0/token".format(tenant), data=body).json()['access_token']
header = { 'Authorization': "Bearer {}".format(token) }
Now try a call to users:
users = requests.get('.0/users', headers=header).json()['value']
len(users)
This results in 100
. So the token works.
Let's look at the JWT token:
decoded = jwt.decode(token, options={'verify_signature': False})['roles']
pprint.pprint(decoded)
This gives a list of roles:
[
...
'eDiscovery.ReadWrite.All',
...
]
The eDiscovery role is in there.
But when I try to get the ediscovery cases:
requests.get('.0/security/cases/ediscoveryCases', headers=header)
<Response [401]>
Joost
I'm trying to automate purges.
I've created an application, given it the Application right eDiscovery.ReadWrite.All
, and given consent.
I've created an oauth2 access token, and verified that the scope is present in the JWT token.
However, when I call https://graph.microsoft/v1.0/security/cases/ediscoveryCases with the Authorization: Bearer {accesstoken}
I get a 401 error.
According to the documentation that I found (https://learn.microsoft/en-us/graph/api/security-casesroot-list-ediscoverycases?view=graph-rest-1.0&tabs=http) the application right should be enough to access this endpoint.
Am I missing something? Do I need to give my app additional rights? Or does (despite what the documentation says) application access not work, and do I need to use delegated permissions?
Code (Python):
Get access token using tenant, app and secret (obviously not given here), and create the header:
body = { "client_id": app, "scope": "https://graph.microsoft/.default", "grant_type": "client_credentials", "client_secret": secret }
token = requests.post("https://login.microsoftonline/{}/oauth2/v2.0/token".format(tenant), data=body).json()['access_token']
header = { 'Authorization': "Bearer {}".format(token) }
Now try a call to users:
users = requests.get('https://graph.microsoft/v1.0/users', headers=header).json()['value']
len(users)
This results in 100
. So the token works.
Let's look at the JWT token:
decoded = jwt.decode(token, options={'verify_signature': False})['roles']
pprint.pprint(decoded)
This gives a list of roles:
[
...
'eDiscovery.ReadWrite.All',
...
]
The eDiscovery role is in there.
But when I try to get the ediscovery cases:
requests.get('https://graph.microsoft/v1.0/security/cases/ediscoveryCases', headers=header)
<Response [401]>
Joost
Share Improve this question edited Mar 19 at 8:10 Haruka Shitou asked Mar 18 at 14:56 Haruka ShitouHaruka Shitou 1591 silver badge8 bronze badges 2- Could you edit your question and add API permissions image of your app registration? Please include how are you generating JWT token too like what authentication flow? – Sridevi Commented Mar 18 at 17:55
- @Sridevi added. – Haruka Shitou Commented Mar 19 at 9:19
4 Answers
Reset to default 1Initially, I registered one application and granted eDiscovery.ReadWrite.All
permission of Application type with consent:
When I ran the python code to list eDiscovery cases with token generated with service principal, I too got same error:
To resolve the error, you need make use of ExchangeOnlineManagement
PowerShell module for creating service principal and assigning role to it.
Before creating service principal, get the object ID and application ID of your registered application from Enterprise Applications tab:
Now, install ExchangeOnlineManagement
module and create service principal by connecting to it with below PowerShell commands:
Install-Module ExchangeOnlineManagement
Import-Module ExchangeOnlineManagement
Connect-IPPSSession
New-ServicePrincipal -AppId "spAppId" -ObjectId "spObjectId" -DisplayName "DiscoveryApp"
Get-ServicePrincipal
Response:
Now, run below PowerShell commands to assign eDiscoveryManager and eDiscoveryAdministrator roles to the service principal:
Add-RoleGroupMember -Identity "eDiscoveryManager" -Member "spObjectId"
Get-RoleGroupMember -Identity "eDiscoveryManager"
Add-eDiscoveryCaseAdmin -User "spObjectId"
Get-eDiscoveryCaseAdmin
Response:
When I ran the python code again after assigning roles to service principal, I got the response successfully like this:
import requests
import json
import jwt
TENANT_ID = "tenantId"
CLIENT_ID = "appId"
CLIENT_SECRET = "secret"
TOKEN_URL = f"https://login.microsoftonline/{TENANT_ID}/oauth2/v2.0/token"
def get_access_token():
body = {
"client_id": CLIENT_ID,
"scope": "https://graph.microsoft/.default",
"grant_type": "client_credentials",
"client_secret": CLIENT_SECRET
}
response = requests.post(TOKEN_URL, data=body)
if response.status_code == 200:
return response.json()["access_token"]
else:
print(f"Error fetching token: {response.status_code} - {response.text}")
return None
def decode_jwt(token):
decoded_token = jwt.decode(token, options={"verify_signature": False})
roles = decoded_token.get("roles", [])
print("\nAssigned Roles in JWT Token:")
print(roles)
print()
return roles
def get_ediscovery_cases(access_token):
url = "https://graph.microsoft/v1.0/security/cases/ediscoveryCases"
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
print(response)
print(response.text)
if __name__ == "__main__":
access_token = get_access_token()
if access_token:
decode_jwt(access_token)
get_ediscovery_cases(access_token)
Response:
Reference:
Set up app-only access for Microsoft Purview eDiscovery by using Microsoft Graph APIs
The eDiscovery.ReadWrite.All just gives your app the permission to use the endpoint but you will still need the underlying eDiscovery permissions to do whatever you want at that level. Because your using Application permissions that means the service principal associated with the app (eg the Enterprise Application in Entra) needs to be given the purview/eDiscovery permissions. https://learn.microsoft/en-us/purview/ediscovery-assign-permissions
Generally your better assigning these rights either via the Entra/Azure portal or PowerShell that will allow you to add the EnterpriseApp to the Compliance Admin role etc the purview portal has some UI short comings.
Here is the scripted solution that I used to implement @Sridevi's answer:
$appname = "YourApplication"
### Connect to Graph (to get the service principal
Connect-MgGraph -NoWelcome
$app = Get-MgServicePrincipal -Filter "displayname eq '$appname'"
Disconnect-MgGraph
### Verify there's exactly one app
$appcount = ($app | measure-object).count
if ($appcount -ne 1) {
throw("$Found $appcount apps with displayname '$appname', this isn't right.")
}
### Connect to IPPS to set everything
Connect-IPPSSession -ShowBanner:$false
$sp = get-serviceprincipal | Where-Object { $_.appid -eq $app.appid }
if (($sp | Measure-Object).count -eq 0) {
try {
$sp = New-ServicePrincipal -AppId $app.appid -ObjectId $app.id -Displayname "$appname - Purge"
} catch {
throw("Can't generate service principal")
}
}
$rolemember = Get-RoleGroupMember -Identity "eDiscoveryManager" | Where-Object { $_.exchangeObjectId -eq $app.id }
if (($rolemember | Measure-Object).count -eq 0) {
Add-RoleGroupMember -Identity "eDiscoveryManager" -Member $app.id
}
$eadmin = Get-eDiscoveryCaseAdmin | Where-Object { $_.exchangeObjectId -eq $app.id }
if (($eadmin | Measure-Object).count -eq 0) {
Add-eDiscoveryCaseAdmin -User $app.id
}
Disconnect-ExchangeOnline
Something does not work yet:
- The SP is created
- The SP is member of the eDiscoveryManager role
- The SP is added to the DiscoveryCaseAdmins
But I get a 500 Internal Server
error when creating a new case, and an empty list of cases when querying them, although there are cases.
cases = requests.get("https://graph.microsoft/v1.0/security/cases/ediscoveryCases", headers=header).json()
>>> cases
{'@odata.context': 'https://graph.microsoft/v1.0/$metadata#security/cases/ediscoveryCases', '@odata.count': 0, 'value': []}
casebody = { 'displayName': "Test via Graph" }
r = requests.post("https://graph.microsoft/v1.0/security/cases/ediscoveryCases", json=casebody, headers=header)
r.json()
{'error': {'code': 'InternalServerError', 'message': 'An unexpected error occurred.', 'innerError': {'date': '2025-03-20T12:31:30', 'request-id': '[id]', 'client-request-id': '[id]'}}}
r.status_code
500