pythonazuremicrosoft-graph-apimsal

OneDrive Picture Download using python msal fails with 401 Error "unauthenticated"


Trying to download my (personal) photos from OneDrive (want to backup them on my NAS)

Created an API Key in Azure Portal. Enterprise applications | All applications

Added "http://localhost" to redirect URL. Using a "Desktop Application" (so no client_secret allowed).

Added the following Rights to the API Key: application rights

    User.Read
    User.Export.All
    Files.ReadWrite.All

Using (python) MSAL library:

$ pip list | grep msal
msal                  1.32.0
msal-extensions       1.3.1

Using PublicClientApplication and "consumers" endpoint to create an app:

app = PublicClientApplication(
    app_id,
    authority="https://login.microsoftonline.com/consumers")

Browser opens, I confirm, browser gets redirected to localhost:

Authentication completed. You can close this window now.

Creating a token with the following scopes:

scopes = ["User.Read", "User.Export.All", "Files.ReadWrite.All"]
result = app.acquire_token_interactive(scopes=scopes)

Token gets returned.

Using that token to Download Infos for the Files stored: request_url = 'https://graph.microsoft.com/v1.0/me/drive/root:/Bilder/Eigene Aufnahmen:/children'

That works fine and returns json information for all files in this folder, e.g.: "@microsoft.graph.downloadUrl": "https://my.microsoftpersonalcontent.com/personal/bfc1c0d9bbadf7cd/_layouts/15/download.aspx?UniqueId=bbadf7cd-c0d9-20c1-80bf-042d00000000&Translate=false&tempauth=v1e.eyJzaXRlaWQiOiI0OTgwMzNhOC00MWVjLTQ4Y2QtYTg2YS0xYjYwNjViMmUyMDUiLCJhcHBfZGlzcGxheW5hbWUiOiJQZXJzb25hbCBadWdyaWZmIGF1ZiBPbmVEcml2ZSIsImFwcGlkIjoiNmNhZTJhMmEtNzAwOS00ZDY2LWE5MjgtYmU1ZjQ[...]

When using this URL to download the Photo, i get:

{"error":{"code":"unauthenticated","message":"Unauthenticated"}}
401

HTTP error 401 and "unauthenticated" message ..

Can someone point me to a direction?

If retrieving information about the folders using the access token works fine, why does it return 401 when trying to download the content of the file?

Full script is here:

#!/usr/bin/python3

import requests
import json
from msal import PublicClientApplication

### config
app_id = '6cae2a2a-XXX'
authority = 'https://login.microsoftonline.com/consumers'
scopes = ["User.Read", "User.Export.All", "Files.ReadWrite.All"]

### start main ###

### create app 
app = PublicClientApplication(app_id, authority=authority)

### get access token
result = app.acquire_token_interactive(scopes)

if "access_token" in result:
    print("Got access_token: " + result["access_token"])
else:
    print(result.get("error"))
    print(result.get("error_description"))
    print(result.get("correlation_id"))  # You may need this when reporting a bug
    exit()

### try to download with access_token
request_url = 'https://graph.microsoft.com/v1.0/me/drive/root:/Bilder/Eigene Aufnahmen:/children'
headers = {"Content-Type": "application/x-www-form-urlencoded",
           "Authorization": "Bearer " + result["access_token"]
           }

response = requests.get(request_url, headers=headers)
print (response.text)
print (response.status_code)
print (json.dumps(response.json(), indent=3))

This works fine (listing all files) .. changing the URL to the Download URL of one of the files gives 401 error ..

As for the permissions the Application has:

$ az ad app permission list --id 6cae2a2a-7009-4d66-a928-XXX
[
  {
    "resourceAccess": [
      {
        "id": "863451e7-0667-486c-a5d6-d135439485f0",
        "type": "Scope"
      },
      {
        "id": "405a51b5-8d8d-430b-9842-8be4b0e9f324",
        "type": "Scope"
      },
      {
        "id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
        "type": "Scope"
      }
    ],
    "resourceAppId": "00000003-0000-0000-c000-000000000000"
  }
]
$ az ad app permission list-grants --id 6cae2a2a-7009-4d66-a928-XXX
[
  {
    "clientId": "64facadb-8227-464e-aa21-XXX",
    "consentType": "AllPrincipals",
    "id": "28r6ZCeCTkaqIZfQJobl9BMQHNlpjqxEqXXX",
    "principalId": null,
    "resourceId": "d91c1013-8e69-44ac-abb8-8508276a8376",
    "scope": " Files.ReadWrite.All User.Export.All User.Read offline_access openid profile"
  }
]

Solution

  • To download the Picture from OneDrive Personal account using Python MSAL

    Initially, I Registered Microsoft Entra ID Application with Supported Account type of Microsoft Personal Account Users only:

    enter image description here

    Configured Authentication tab with Mobile and desktop applications and Added Redirect_URI:

    enter image description here

    Added delegated type Files.ReadWrite.All API permission like below:

    enter image description here

    {"error":{"code":"unauthenticated","message":"Unauthenticated"}}
    

    To resolve the error you are getting, Needs to use tempauth in Authorization.

    import requests
    import msal
    import re
    
    # Replace with your App Registration details
    CLIENT_ID = "<APPLICATION_ID>"  # Azure App Registration Client ID
    AUTHORITY = "https://login.microsoftonline.com/consumers"  # Use 'consumers' for personal accounts
    SCOPES = ["Files.ReadWrite.All"]
    
    # Initialize MSAL Public Client
    app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
    
    # Authenticate interactively (opens browser)
    token_result = app.acquire_token_interactive(SCOPES)
    if not token_result or "access_token" not in token_result:
        print("[!] Authentication failed.")
        exit()
    
    access_token = token_result["access_token"]
    headers = {"Authorization": f"Bearer {access_token}"}
    
    # File name (from OneDrive root)
    file_name = "<FILE-PATH>"
    
    # Request URL to get file metadata
    file_metadata_url = f"https://graph.microsoft.com/v1.0/me/drive/root:/{file_name}"
    
    # Get file metadata
    response = requests.get(file_metadata_url, headers=headers)
    if response.status_code != 200:
        print(f"[!] Error getting file details: {response.json()}")
        exit()
    
    # Extract download URL
    file_metadata = response.json()
    download_url = file_metadata.get("@microsoft.graph.downloadUrl")
    
    if not download_url:
        print("[!] Download link not found.")
        exit()
    
    print(f"Download URL: {download_url}")
    
    # Extract 'tempauth' token from URL
    tempauth_match = re.search(r"tempauth=([^&]+)", download_url)
    if not tempauth_match:
        print("[!] Failed to extract 'tempauth' token.")
        exit()
    
    tempauth_token = tempauth_match.group(1)
    
    # Set new headers using 'tempauth' instead of Bearer token
    download_headers = {"Authorization": f"Bearer {tempauth_token}"}
    
    # Download file
    download_response = requests.get(download_url, headers=download_headers)
    
    if download_response.status_code == 200:
        with open(file_name, "wb") as file:
            file.write(download_response.content)
        print(f" File downloaded successfully: {file_name}")
    else:
        print(f"[!] Error downloading file: {download_response.status_code}")
        print(download_response.text)
    
    
    

    Grant the permission to application to access your OneDrive Personal Account:

    enter image description here enter image description here

    Response:

    enter image description here

    Also, When I clicked that download URL:

    enter image description here