pythonjsonclient

AADSTS900144: The request body must contain the following parameter: 'grant_type' with Microsoft Defender for Endpoint API


I am attempting to authenticate with Microsoft Defender for Endpoint's API service by following this learn article:

https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/run-advanced-query-sample-python?view=o365-worldwide#get-token

I typically use the "request" library for REST calls, so I didn't follow the above code snippet exactly. When running my version of the above code:

import json

import requests


MDE_CLIENT_ID = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX'
MDE_CLIENT_SECRET = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
TENANT_ID = 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX'
AUTHORITY = 'https://login.microsoftonline.com/'
MDE_URI = 'https://api.securitycenter.microsoft.com'


class RESTError(Exception):
    def __init__(self, status_code, message):
        self.status_code = status_code
        self.message = str(self.status_code) + ' ' + json.dumps(message)
        super().__init__(self.message)

def authenticate_mde():
    headers = {
        'content-type': 'application/x-www-form-urlencoded'
    }
    body = {
        'resource': MDE_URI,
        'client_id': MDE_CLIENT_ID,
        'client_secret': MDE_CLIENT_SECRET,
        'grant_type': 'client_credentials'
    }
    response = requests.post(AUTHORITY + TENANT_ID + '/oauth2/token', data = json.dumps(body), headers = headers)
    
    if (response.status_code < 200 or response.status_code > 299):
        raise RESTError(response.status_code, response.json())

    return response.json()['access_token']


def main():
    token = authenticate_mde()
    print(token)

if (__name__ == '__main__'):
    main()

When I run this code I receive a 400 error back from the authentication service complaining about a missing body parameter 'grant_type'. However, as you can see in the code, I clearly have that included in the same fashion as the code snippet from MSFT.

Traceback (most recent call last):
  File "C:\Users\24724\Documents\code\python\scripts\mde-executor.py", line 42, in <module>
    main()
  File "C:\Users\24724\Documents\code\python\scripts\mde-executor.py", line 38, in main
    token = authenticate_mde()
  File "C:\Users\24724\Documents\code\python\scripts\mde-executor.py", line 32, in authenticate_mde
    raise RESTError(response.status_code, response.json())
__main__.RESTError: 400 {"error": "invalid_request", "error_description": "AADSTS900144: The request body must contain the following parameter: 'grant_type'.\r\nTrace ID: e4d0d06e-aae6-4b6d-80e2-2b3997f74302\r\nCorrelation ID: 5788089d-f94e-4e9a-8667-d6e36c183af8\r\nTimestamp: 2023-01-06 17:00:23Z", "error_codes": [900144], "timestamp": "2023-01-06 17:00:23Z", "trace_id": "e4d0d06e-aae6-4b6d-80e2-2b3997f74302", "correlation_id": "5788089d-f94e-4e9a-8667-d6e36c183af8", "error_uri": "https://login.microsoftonline.com/error?code=900144"}

I also tried copying MSFT's code snippet exactly and inserting my own global var info but receive the same error. I have tried moving the body to url parameters, headers, splitting it up between body, params, and headers. No luck. I have tried different content-types in the header as well and tried without any headers. None seems to work and I am stumped at this point.


Solution

  • I resolved the issue. Passing 'resource' into the body was apparently screwing it up, even though their python example here shows that:

    import json
    import urllib.request
    import urllib.parse
    
    tenantId = '00000000-0000-0000-0000-000000000000' # Paste your own tenant ID here
    appId = '11111111-1111-1111-1111-111111111111' # Paste your own app ID here
    appSecret = '22222222-2222-2222-2222-222222222222' # Paste your own app secret here
    
    url = "https://login.microsoftonline.com/%s/oauth2/token" % (tenantId)
    
    resourceAppIdUri = 'https://api.securitycenter.microsoft.com'
    
    body = {
        'resource' : resourceAppIdUri,
        'client_id' : appId,
        'client_secret' : appSecret,
        'grant_type' : 'client_credentials'
    }
    
    data = urllib.parse.urlencode(body).encode("utf-8")
    
    req = urllib.request.Request(url, data)
    response = urllib.request.urlopen(req)
    jsonResponse = json.loads(response.read())
    aadToken = jsonResponse["access_token"]
    

    https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/run-advanced-query-sample-python?view=o365-worldwide#get-token

    Following the example they give for cURL here and using the 'scope' parameter instead fixed it.

    curl -i -X POST -H "Content-Type:application/x-www-form-urlencoded" -d "grant_type=client_credentials" -d "client_id=%CLIENT_ID%" -d "scope=https://securitycenter.onmicrosoft.com/windowsatpservice/.default" -d "client_secret=%CLIENT_SECRET%" "https://login.microsoftonline.com/%TENANT_ID%/oauth2/v2.0/token" -k
    

    https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/exposed-apis-create-app-webapp?view=o365-worldwide#use-curl