Search code examples
pythonazureodatams-projectazure-app-registration

How to authorise to MS Project ODATA API by using ClientCredentialFlow?


I am using the Python msal package and the ClientCredentialFlow when trying to authenticate to the MS-project odata API using following script:

import requests
import msal

def get_access_token():
    # Set your Azure AD tenant ID, client ID (Application ID), and client secret
    tenant_id = "your_tenant_id"
    client_id = "your_client_id"
    client_secret = "your_client_secret"

    # Create a confidential client application
    confidential_client = msal.ConfidentialClientApplication(
        client_id,
        authority=f"https://login.microsoftonline.com/{tenant_id}",
        client_credential=client_secret
    )

    # Define the scope (permissions) you want to request
    scopes = ["https://your_tenant_name.sharepoint.com/.default"]  # Replace "your_tenant_name" with your actual tenant name

    # Get an access token using the "Client Credentials" flow
    result = confidential_client.acquire_token_for_client(scopes=scopes)

    if "access_token" in result:
        access_token = result["access_token"]
        return access_token
    else:
        print("Failed to obtain access token.")
        return None

def get_project_data():
    # Set the API endpoint URL
    ms_project_api_url = 'https://your_tenant_name.sharepoint.com/sites/your_project_instance/_api/ProjectData/'

    # Get the access token
    access_token = get_access_token()

    if access_token:
        # Create the headers with the access token
        headers = {
            'Authorization': f'Bearer {access_token}',
            'Content-Type': 'application/json'
        }

        # Make the API request
        response = requests.get(ms_project_api_url, headers=headers)

        if response.status_code == 200:
            # The request was successful, you can process the response here
            print("Data successfully retrieved:")
            print(response.json())  # If the API returns JSON data
        else:
            # The request was not successful, display the error message
            print("Error while retrieving data. Status code:", response.status_code)
            print(response.text)
    else:
        print("Failed to obtain access token.")

if __name__ == "__main__":
    get_project_data()

Which scope do I need and what permissions are needed in the Appregistration via Azure?

As far as I understood I need some application-permissions, but can't find some which belong to MS Project. I just found following delegated permissions in SharePoint group: Project.Read, Project.Write, ProjectWebApp.FullControl, ProjectWebAppReporting.Read and tried them out:

screenshot of delegated permissions in Appregistry

I tried with different scopes, e.g.: ["https://mytenant.sharepoint.com/.default"] here I got an access-token, but no access to the API, get_project_data() fails with:

The request was not valid or is malformed.


Solution

  • Note that, client credentials flow only works with permissions of Application type but they are not currently available for MS Project.

    I registered one Azure AD application and added below Delegated API permissions by granting consent:

    enter image description here

    When I ran your code in my environment, I too got same error as below:

    import requests
    import msal
    
    def get_access_token():
        # Set your Azure AD tenant ID, client ID (Application ID), and client secret
        tenant_id = "tenantID"
        client_id = "appID"
        client_secret = "secret"
    
        # Create a confidential client application
        confidential_client = msal.ConfidentialClientApplication(
            client_id,
            authority=f"https://login.microsoftonline.com/{tenant_id}",
            client_credential=client_secret
        )
    
        # Define the scope (permissions) you want to request
        scopes = ["https://xxxxxxxxxx.sharepoint.com/.default"]  # Replace "your_tenant_name" with your actual tenant name
    
        # Get an access token using the "Client Credentials" flow
        result = confidential_client.acquire_token_for_client(scopes=scopes)
    
        if "access_token" in result:
            access_token = result["access_token"]
            return access_token
        else:
            print("Failed to obtain access token.")
            return None
    
    def get_project_data():
        # Set the API endpoint URL
        ms_project_api_url = 'https://xxxxxxxxxx.sharepoint.com/sites/pwademosite/_api/ProjectData/'
    
        # Get the access token
        access_token = get_access_token()
        
        #print(access_token)
    
        if access_token:
            # Create the headers with the access token
            headers = {
                'Authorization': f'Bearer {access_token}',
                'Content-Type': 'application/json'
            }
    
            # Make the API request
            response = requests.get(ms_project_api_url, headers=headers)
    
            if response.status_code == 200:
                # The request was successful, you can process the response here
                print("Data successfully retrieved:")
                print(response.json())  # If the API returns JSON data
            else:
                # The request was not successful, display the error message
                print("Error while retrieving data. Status code:", response.status_code)
                print(response.text)
        else:
            print("Failed to obtain access token.")
    
    if __name__ == "__main__":
        get_project_data()
    

    Response:

    enter image description here

    To authorize to MS Project ODATA API, you need to make use of Delegated flows like username password, interactive flows etc... that works with permissions of Delegated type.

    In my case, I changed to username password flow for which below option should be enabled(wait for few minutes after enabling as there will be delay):

    enter image description here

    When I ran below modified code with username password flow, I got Project data response successfully:

    import requests
    import msal
    
    def get_access_token():
        # Set your Azure AD tenant ID, client ID (Application ID), and client secret
        tenant_id = "tenantID"
        client_id = "appID"
        username = "admin@xxxxxxxxxx.onmicrosoft.com"
        password = "xxxxxxxxxxxx"
    
        # Create a public client application
        public_client = msal.PublicClientApplication(
            client_id,
            authority=f"https://login.microsoftonline.com/{tenant_id}",
        )
    
        # Define the scope (permissions) you want to request
        scopes = ["https://xxxxxxxx.sharepoint.com/.default"]  # Replace "your_tenant_name" with your actual tenant name
    
        # Get an access token using the "Username Password" flow
        result = public_client.acquire_token_by_username_password(
        username=username,
        password=password,
        scopes=scopes,
    )
    
        if "access_token" in result:
            access_token = result["access_token"]
            return access_token
        else:
            print("Failed to obtain access token.")
            return None
    
    def get_project_data():
        # Set the API endpoint URL
        ms_project_api_url = 'https://xxxxxxxxx.sharepoint.com/sites/pwademosite/_api/ProjectData/'
    
        # Get the access token
        access_token = get_access_token()
        
        #print(access_token)
    
        if access_token:
            # Create the headers with the access token
            headers = {
                'Authorization': f'Bearer {access_token}',
                'Content-Type': 'application/json'
            }
    
            # Make the API request
            response = requests.get(ms_project_api_url, headers=headers)
    
            if response.status_code == 200:
                # The request was successful, you can process the response here
                print("\nData successfully retrieved:\n")
                print(response.content)  
            else:
                # The request was not successful, display the error message
                print("Error while retrieving data. Status code:", response.status_code)
                print(response.text)
        else:
            print("Failed to obtain access token.")
    
    if __name__ == "__main__":
        get_project_data()
    

    Response:

    enter image description here