2
votes

I am trying to download some data from a customer's Microsoft Dyanmics 365 CRM API. Below is my code written in Python 3.7, using MSAL (Microsoft Authentication Library) for Python.

import msal
import requests


class Downloader:
    
    AUTHORITY = 'https://login.microsoftonline.com/common'
    SCOPES = ["https://admin.services.crm.dynamics.com/user_impersonation"]

    def __init__(self, client_id, client_credential, refresh_token):
        self.client_id = client_id
        self.client_credential = client_credential
        self.refresh_token = refresh_token

    def download(self):

        application = msal.ConfidentialClientApplication(
            client_id=self.client_id,
            client_credential=self.client_credential,
            authority=self.AUTHORITY,
            token_cache=None)

        response = application.acquire_token_by_refresh_token(self.refresh_token, self.SCOPES)

        instances = requests.get(
            'https://globaldisco.crm.dynamics.com/api/discovery/v1.0/Instances',
            headers={'Authorization': 'Bearer ' + response['access_token']},
        )

        return response

When I run the download() method I successfully get a response that contains an Access Token but when I use it to GET instances I get back HTTP Status Code 401 Access Denied.

My question is: Why am I getting 401 Access Denied and how can I fix my code to get 200 OK?

More data:

  1. client_id and client_credential identify a multi-tenant Azure App Registration I've setup. This app registration is not verified yet. It has these API Permissions (scopes) configured:
  • https://admin.services.crm.dynamics.com/user_impersonation
  • https://graph.microsoft.com/User.Read
  1. refresh_token has been generated based on Quickstart: Add sign-in with Microsoft to a Python web app. The quickstart goes through setting up a small Flask web application where a person can sign in via Microsoft Identity and grant permission for my app registration to impersonate them when later accessing the Dynamics 365 CRM API. I've setup the Flask app to use my App Registration and the two scopes above.

  2. Because my App Registration is not verified yet, I had an admin of the target tenet sign in via the Flask app, approve and give consent for my app registration to be used by the tenet org. I've tried both their refresh token and another non-admin person's refresh token. Both yield 401.

  3. The tenet org (to which my customers' identities belong) is linked to a Dynamics 365 account.

Update:

When I view my app registration in my customer's (i.e. tenet's) Azure Portal under Enterprise Applications I can see the Common Data Service user_impersonation permission under the Admin consent tab but it's missing from the User consent tab.

Screenshot of Admin consent tab in Enterprise Application window

I'm trying to figure out whether this might explain why users are denied access and how to add the missing permission to User consent.

1

1 Answers

0
votes

Has the user been added to CRM? The identity you are using should still have full roles assigned within CRM. Someone should have added that app registration to the list of users in CRM. Settings->Security (change the list view to "Application Users") press "New", change the form from "user" to "Application User". Set the Name, copy the application Id from the Application registration. The email can exist or not.