python-o365: Connection.request_token: missing_code-error despite single tenant application

Hi there! I’m trying to deal with microsoft graph api via O365. As far as I understand the usage of an Authorization Code is not necessary when dealing with single tenant applications. My code is the following:

credentials = ('client_id', 'client_secret')
scopes = ['http://graph.microsoft.com/Mail.Read']
protocol_graph = MSGraphProtocol()
scopes_graph = protocol_graph.get_scopes_for(scopes)
con = Connection(credentials, scopes=scopes_graph, tenant_id='tenant_id')
url, state = con.get_authorization_url()
con.request_token(url)

request_token gives me: Unable to fetch auth token. Error: (missing_code) Missing code parameter in response. despite there’s no need of a code since dealing with single tenant application.

I’m able to get a token via:

payload = {'client_id': 'client_id',
           'client_secret': 'client_secret',
           'grant_type': 'client_credentials',
           'scope': 'Mail.Read'}

r = requests.get('https://login.microsoftonline.com/tenant_id/oauth2/token',
                 data=payload, headers={'Content-Type': 'application/x-www-form-urlencoded'})
token = json.loads(r.content)['access_token']

Is there a workaround to set the token manually without usage of request_token? Many thanks!

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 28 (28 by maintainers)

Commits related to this issue

Most upvoted comments

Thank you very much @teamoo

With tenant = ‘common’ I get the following exception:

Unable to fetch auth token. Error: (unauthorized_client) AADSTS700016: Application with identifier 'XXXX-XXXX-XXXXX-XXXXXX' was not found in the directory 'microsoft.com'. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You may have sent your authentication request to the wrong tenant.

So it is clear that the tenant id is definitely needed.

1. the Account.authenticate method still needs a scope argument according to your code (TypeError: authenticate() missing 1 required keyword-only argument: 'scopes'). So I changed it to account.authenticate(auth_flow_type='backend',scopes=scopes). I think you should provide a default value (=None) in the authenticate Method?

That’s true because if you pass None to the authenticate scopes parameter, the authenticate method will still use the scopes provided on the Account init

2. Unfortunately it is not working: Unable to fetch auth token. Error: (invalid_request) AADSTS90014: The required field 'scope' is missing. Problem is that the oauth2_session has no scope set. This could be fixed by adding the scope=scopes[0] parameter to the fetch_token method call called by request_token_client_credentials_flow.

I don’t understand this error. The scopes are passed to the Oauth2Session object on in the get_session method (same for the web flow… that works).

Anyway… passing the scope parameter to fetch token solves this.

2. But I think this is wrong. I think the right way would be to create the BackendApplicationClient with the scope parameter, which is currently not supplied.

No, not needed.

Another question: The tokens for this flow always last one hour, and there is no refresh token mechanism. Would the module automatically request a new token if the old one is expired?

Yes, It should ask for another access token. I’ll see how to do it.

This is what works for me:

client_id = config["msgraph"]["client_id"]
client_secret = config["msgraph"]["client_secret"]
auth_token_url = "https://login.microsoftonline.com/"+config["msgraph"]["tenant_id"]+"/oauth2/v2.0/token"
resource_uri = config["msgraph"]["resource_uri"]
scopes = ["https://graph.microsoft.com/.default"]

credentials = (client_id, client_secret)
msgraph_payload={
                        "client_id" : client_id,
                        "client_secret" : client_secret,
                        "scope" : "https://graph.microsoft.com/.default",
                        "grant_type" : "client_credentials"
                    }

token_backend = FileSystemTokenBackend(token_path="", token_filename="msgraph.token")

account = Account(credentials, token_backend=token_backend, tenant_id=config["msgraph"]["tenant_id"],scopes=scopes,main_resource="")

account.protocol.default_resource = ""
account.main_resource = ""

if not account.is_authenticated:
    r = account.connection.get_session().post(url=auth_token_url,data=msgraph_payload)

    token = r.json()
    
    if "access_token" in token:
        account.connection.token_backend.token["access_token"] = token["access_token"]
        account.connection.token_backend.token["expires_at"] = int(time.time() + token["expires_in"])
        account.connection.token_backend.save_token()