Search code examples
pythonmicrosoft-graph-apimicrosoft-graph-security

Obtain authorization access token using /token endpoint


I am working on creating a python application which will use the Microsoft Security Graph APIs. I have followed the sample provided here with no problems. I now want to be able to create a python application which can obtain the access token (and refresh it when needed) without using a web browser.

So far I have created a new application with SecurityEvent.Read.All and SecurityEvent.ReadWrite.All permissions under both Delegated Permissions and Application Permissions. I then went to the following url in a web browser to grant my application consent and logged in with my tenant admin:

https://login.microsoftonline.com/common/adminconsent?
        client_id=APPLICATION_ID
        &state=12345
        &redirect_uri=REDIRECT_URL  

Next, I assume I want to follow the steps here to make a POST call to obtain the token. Below is an example of how I am doing that.

d = {
    "client_id": <client_id>,
    "scope": ["https://graph.microsoft.com/.default"],
    "client_secret": <client_secret>,
    "grant_type": "client_credentials"
}
r = requests.post("https://login.microsoftonline.com/common/oauth2/v2.0/token", data=d)

The following is the response I recieve

{
  "token_type": "Bearer",
  "expires_in": 3600,
  "ext_expires_in": 0,
  "access_token": "eyJ0eXAiOiJKV1QiLCJub25jZSI6IkFRQUJBQUFBQUFDNXVuYTBFVUZnVElGOEVsYXh0V2pUR2cxQV9PR0FJWmx3T1V0b2hMNHdWN2hURHVoQTJSTzIyQnY0cGljcGJ2UmkwdEdpcmY0Q2cxaDhRZF9RamUzX2l0LUhfT1VhTnJRaDFxYXpINWtIRENBQSIsImFsZyI6IlJTMjU2IiwieDV0IjoiaTZsR2szRlp6eFJjVWIyQzNuRVE3c3lISmxZIiwia2lkIjoiaTZsR2szRlp6eFJjVWIyQzNuRVE3c3lISmxZIn0.eyJhdWQiOiJodHRwczovL2dyYXBoLm1pY3Jvc29mdC5jb20iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDcvIiwiaWF0IjoxNTQwNDE3MjI0LCJuYmYiOjE1NDA0MTcyMjQsImV4cCI6MTU0MDQyMTEyNCwiYWlvIjoiNDJSZ1lMRDUvK2RINkplbC9EK1RHRmZlaVNqMUJnQT0iLCJhcHBfZGlzcGxheW5hbWUiOiJTZWN1cml0eSBHcmFwaCBQT0MiLCJhcHBpZCI6IjMxMjA0MGRmLWIyZmUtNDI1Ni04ZWZkLTk1NDYyOTVjNWZhNyIsImFwcGlkYWNyIjoiMSIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0LzcyZjk4OGJmLTg2ZjEtNDFhZi05MWFiLTJkN2NkMDExZGI0Ny8iLCJ0aWQiOiI3MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDciLCJ1dGkiOiJnLUtlY1dkZXcwQzV2cjBoLUhGZ0FBIiwidmVyIjoiMS4wIiwieG1zX3RjZHQiOjEyODkyNDE1NDd9.JGu6fjJk_vVvG_4NYRBfZto6nW9YRWh43JzhrlcFqFYAnJSJvWDlHbzka_H3gUKkZernQanzjI6AumER9mOtapmj1qhu_58pCuL2lTl2ubj1MTBTYOpUX3hlKgN16AeyvjO1x95LKDO9xAcIYLXEmwbkNw87x7YxZ1lKBA59c1BCCILmqMf86E7CDExf7EPqbqAPdCI6FPkStx5CJ0YnvAN2Uk5EHloTL3BTXMqMmT05h7OAvZRogkIk4aeGof1OXKcqXw7dJbzYg8XiEeXdAYhA1ld6VEwiVBMSpqf4w476Ksvr8JUbg-xhAmGoU8CrXBB4em5Gv2ko89-qP49nUA"
}

With the now obtained access token, I am trying to call the /alerts endpoint. Below is how that is being done.

headers = {
    "Content-type": "application/json",
    "Authorization": "Bearer " + <access_token>,
}
alerts = requests.get("https://graph.microsoft.com/v1.0/security/alerts", headers=headers)

Instead of getting alerts returned back this is what the response looks like to me:

{
    "error": {
    "code": "UnknownError",
    "message": "Auth token does not contain valid permissions or user does not have valid roles. Please see document at https://techcommunity.microsoft.com/t5/Using-Microsoft-Graph-Security/Authorization-and-Microsoft-Graph-Security-API/m-p/184376",
    "innerError": {
      "request-id": "1319d099-7b14-4eb0-9834-4614d5231085",
      "date": "2018-10-24T21:23:16"
    }
  }
}

Do I somehow have the permissions wrong?


Solution

  • You're extremely close. The issue here is that the scope value https://graph.microsoft.com/v1.0/security/alerts/.default is incorrect.

    If you're looking to use the Security APIs, you would want to include either SecurityEvents.Read.All or SecurityEvents.ReadWrite.All in your app's registration (via https://apps.dev.microsoft.com).

    Important: Whenever you change the scopes for your application, you must repeat the Admin Consent process. Admin Consent only applies to the scopes you had configured at the time Consent was granted.

    Once this is complete, you need to tell the /token endpoint to use the pre-registered scopes. This is done by setting the scope property to https://graph.microsoft.com/.default. What you're doing here is telling it you want to use the Pre-registered (.default) scopes for Microsoft Graph (https://graph.microsoft.com/):

    d = {
        "client_id": <client_id>,
        "scope": ["https://graph.microsoft.com/.default"],
        "client_secret": <client_secret>,
        "grant_type": "client_credentials"
    }
    r = requests.post("https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token", data=d)
    

    UPDATE

    There is an additional issue with the /token endpoint you're using. When using client_credentials you cannot use the /common tenant. Since there isn't a user authenticating, there is no way for AAD to discover which tenant it should authenticate against. Without knowing which tenant, it cannot determine which scopes should get applied, which in turn, results in a token without any scopes.

    You need to explicitly provide the tenant ID you're looking to obtain the token for. You can either use the Tenant's ID (a GUID) or Domain:

    https://login.microsoftonline.com/00000000-0000-0000-0000-000000000000/oauth2/v2.0/token
    
    https://login.microsoftonline.com/tenantdomain.onmicrosoft.com/oauth2/v2.0/token