pythonflaskonedriveazure-ad-msal

Issue accessing personal OneDrive files with Python, Flask, MSAL and auth_code_flow


Trying to access the "https://graph.microsoft.com/v1.0/me/drive" endpoint via a Flask application results in the following response:

{
"error": {
"code": "HostNotFound",
"innerError": {
"client-request-id": "{xxx}",
"date": "2024-04-02T15:47:03",
"request-id": "{yyy}"
},
"message": "Target '{zzz}-my.sharepoint.com' is not found."
}
  1. I am able to retrieve the data with my personal account using the Graph Explorer
  2. I have the correct permissions assigned in Azure.
print(token["scope"])
# openid profile email https://graph.microsoft.com/Application.Read.All https://graph.microsoft.com/Files.Read https://graph.microsoft.com/Files.Read.All https://graph.microsoft.com/Files.ReadWrite.All https://graph.microsoft.com/Sites.Read.All https://graph.microsoft.com/Sites.ReadWrite.All https://graph.microsoft.com/User.Export.All https://graph.microsoft.com/User.Read https://graph.microsoft.com/.default
  1. I am using auth code flow and can successfully retrieve data from other endpoints of Microsoft Graph, e.g. "https://graph.microsoft.com/v1.0/applications" or "https://graph.microsoft.com/v1.0/me" Therefore, I assume that the token generation is not the issue.

As I am still trying to get the access to work in the first place, the tokens are currently passed around via session, with the decorator executing the final request after appending the Authorization header. This is not my intended long term use / implementation. I am happy about any suggestion / industry standard, how to properly handle and incorporate this. However, the focus of this question is mainly about why I am not able to access my personal OneDrive files via the Microsoft Graph API.

app.py

import json
from functools import wraps

import msal
import requests
from flask import Flask, session, request, redirect, make_response, url_for

app = Flask(__name__)

with open("delegated_config.json") as file:
    config = json.load(file)

app.config.update(config)

client_instance = msal.ConfidentialClientApplication(
    client_id=app.config["application_id"],
    client_credential=app.config["client_secret"],
    authority=app.config["authority_url"]
)


def initiate_auth_flow():
    """Authentication with Access Token"""
    if not session.get("auth_flow"):
        authorization = client_instance.initiate_auth_code_flow(
            app.config["scope"], redirect_uri=app.config["redirect_uri"]
        )
        session["auth_flow"] = authorization

    authorization = session.get("auth_flow")
    # Will redirect to "generate_token()"
    return make_response(redirect(authorization["auth_uri"]))


def get_token(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if not session.get("access_token"):
            session["execute_func"] = func.__name__
            return initiate_auth_flow()
        else:
            url = func(*args, **kwargs)
            return requests.get(
                url, headers={"Authorization": f"Bearer {session['access_token']}"}).json()

    return wrapper


# Redirect route after logging in and confirming permissions
@app.route("/generate_token")
def generate_token():
    query_string = request.args
    token = client_instance.acquire_token_by_auth_code_flow(session["auth_flow"],
                                                            auth_response=query_string)
    session["access_token"] = token["access_token"]
    return redirect(url_for(session["execute_func"]))



# WORKS
@app.route("/me")
@get_token
def me():
    return "https://graph.microsoft.com/v1.0/me"



# WORKS
@app.route("/all_applications")
@get_token
def all_applications():
    return "https://graph.microsoft.com/v1.0/applications"



# DOES NOT WORK, ERROR AT BEGINNING OF QUESTION
@app.route("/all_drives")
@get_token
def all_drives():
    return "https://graph.microsoft.com/v1.0/me/drive"



# DOES NOT WORK
@app.route("/private_drive")
@get_token
def private_drive():
    return "https://api.onedrive.com/v1.0/me/drive"
# {"error": {"code": "invalidRequest", "message": "Invalid API or resource"}}

delegated_config.json

{
  "SECRET_KEY": "{xxx}",
  "application_id": "{yyy}",
  "client_secret": "{zzz}",
  "authority_url": "https://login.microsoftonline.com/{tenant_id}",
  "scope": [ "https://graph.microsoft.com/.default" ],
  "redirect_uri": "http://localhost:6123/generate_token"
}

Solution

  • Eventually figured it out myself. All I had to do is to NOT pass the specific authority_url of my tenant to the ConfidentialClientApplication, so i.e.

    client_instance = msal.ConfidentialClientApplication(
        client_id=app.config["application_id"],
        client_credential=app.config["client_secret"]
    )
    

    ...which will lead to using the default authority_url, i.e. https://login.microsoftonline.com/common.

    Thinking about it afterwards, it makes sense. The initial error code indicated, that Microsoft Graph was not able to find a OneDrive / Sharepoint for my user in the tenant. Since I wanted to access the data of my personal OneDrive however, which is not included in my organization, I was simply able to access it using the /commonendpoint, after authenticating myself with the application.