Reputation: 2836
Thank you in advance for your help.
I am trying to access email messages for a specific email account for an organization that uses Microsoft Graph.
The Python app I am creating needs to read and forward email messages that are in this specific email account at "[email protected]". (I previously successfully read the emails using the Python library exchangelib, but now Basic Auth is no longer supported by Microsoft; exchangelib does not support Microsoft Graph).
The organization has registered an application "Email Service App" with the Microsoft identity platform. It appears that full permissions have been granted. See the screenshot the organization sent to me below. (I do not have access to the Graph Dashboard.)
I have been following the excellent example provided in the GitHub repository for the msal library: https://github.com/AzureAD/microsoft-authentication-library-for-python/blob/dev/sample/confidential_client_secret_sample.py
The code I have written does successfully obtain a bearer token.
My expectation is that with that bearer token I could read emails for a specific email account: [email protected]
The error from the code is simple: "Insufficient privileges to complete the operation."
I am nonetheless confused because it does appear that full permissions have been granted. (See above screenshot.)
Here is the code I am using:
config_data = {
"authority": "https://login.microsoftonline.com/<secret value>",
"client_id": "<secret value>",
"scope": ["https://graph.microsoft.com/.default"],
"secret": "<secret value>",
"endpoint": "https://graph.microsoft.com/v1.0/users"
}
# Optional logging
logging.basicConfig(level=logging.DEBUG) # Enable DEBUG log for entire script
logging.getLogger("msal").setLevel(logging.INFO) # Optionally disable MSAL DEBUG logs
config = json.loads(config_data)
# Create a preferably long-lived app instance which maintains a token cache.
app = msal.ConfidentialClientApplication(
config["client_id"], authority=config["authority"],
client_credential=config["secret"],
)
LOG OUTPUT:
DEBUG:urllib3.util.retry:Converted retries value: 1 -> Retry(total=1, connect=None, read=None, redirect=None, status=None)
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): login.microsoftonline.com:443
DEBUG:urllib3.connectionpool:https://login.microsoftonline.com:443 "GET /<secret>/v2.0/.well-known/openid-configuration HTTP/1.1" 200 1753
Code continues:
# The pattern to acquire a token looks like this.
result = None
# Firstly, looks up a token from cache
# Since we are looking for token for the current app, NOT for an end user,
# notice we give account parameter as None.
result = app.acquire_token_silent(config["scope"], account=None)
LOG OUTPUT:
INFO:root:No suitable token exists in cache. Let's get a new one from AAD.
DEBUG:urllib3.connectionpool:https://login.microsoftonline.com:443 "POST /<secret>/oauth2/v2.0/token HTTP/1.1" 200 1589
Code continues:
if not result:
logging.info("No suitable token exists in cache. Let's get a new one from AAD.")
result = app.acquire_token_for_client(scopes=config["scope"])
LOG OUTPUT:
INFO:root:No suitable token exists in cache. Let's get a new one from AAD.
DEBUG:urllib3.connectionpool:https://login.microsoftonline.com:443 "POST /<secret>/oauth2/v2.0/token HTTP/1.1" 200 1589
Code continues:
print(result)
OUTPUT:
{'token_type': 'Bearer',
'expires_in': 3599,
'ext_expires_in': 3599,
'access_token': <removed, was successful>}
Code continues:
if "access_token" in result:
# Calling graph using the access token
graph_data = requests.get( # Use token to call downstream service
config["endpoint"],
headers={'Authorization': 'Bearer ' + result['access_token']},).json()
print("Graph API call result: %s" % json.dumps(graph_data, indent=2))
else:
print(result.get("error"))
print(result.get("error_description"))
print(result.get("correlation_id")) # You may need this when reporting a bug
LOG OUTPUT:
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): graph.microsoft.com:443
DEBUG:urllib3.connectionpool:https://graph.microsoft.com:443 "GET /v1.0/users HTTP/1.1" 403 None
Graph API call result: {
"error": {
"code": "Authorization_RequestDenied",
"message": "Insufficient privileges to complete the operation.",
"innerError": {
"date": "2022-11-06T13:07:13",
"request-id": "13a2593f-e9c9-4844-aca5-7c362a7f83b8",
"client-request-id": "13a2593f-e9c9-4844-aca5-7c362a7f83b8"
}
}
}
Is the issue with the code I am using or are there some other settings in Microsoft Graph that need to be set by the owning organization? Or is it another issue?
Thank you again for your help.
Upvotes: 0
Views: 1365
Reputation: 22032
The problem is your trying to use the client credentials flow but all your permission are delegate permissions which aren't valid for that flow. You need to assign App permission and consent to them eg something like
By default this will give you access to every mailbox in the tenant as you mentioned you only want to access one mailbox you can then scope the permission down https://learn.microsoft.com/en-us/graph/auth-limit-mailbox-access
Upvotes: 3