Microsoft Graph permission scopes throwing error when I pass more than one scope

Viewed 495

I need to programmatically upload files to OneDrive. I won't have a front-end to generate the keys, because of that I need to generate them without user interaction and that's my problem. I've already managed to do this with a simple POC, but it necessarily needs user interaction because it uses the login and password credentials to authenticate to the Microsoft account, and only after that it uses clientId and clientSecret

When I try to generate a token using @msal-node, it doesn't use the scopes defined in Azure.

const msal = require('@azure/msal-node')
const config = require('../config')
const azConfig = {
  auth: {
    clientId: config.azureAppId,
    authority: `https://login.microsoftonline.com/${config.azureTenantId}`,
    clientSecret: config.azureAuthSecret
  }
}

const pca = new msal.ConfidentialClientApplication(azConfig)

return await pca.acquireTokenByClientCredential({
  scopes: [`${config.azureAppId}/.default`]
})

And I also can't pass new scopes through the acquireTokenByClientCredential() function because it thrown a error.

const msal = require('@azure/msal-node')
const config = require('../config')
const azConfig = {
  auth: {
    clientId: config.azureAppId,
    authority: `https://login.microsoftonline.com/${config.azureTenantId}`,
    clientSecret: config.azureAuthSecret
  }
}

const pca = new msal.ConfidentialClientApplication(azConfig)

return await pca.acquireTokenByClientCredential({
  scopes: [
    `${config.azureAppId}/.default`,
    'Files.Read'
  ]
})

ServerError: invalid_scope: 70011 - [2021-12-30 01:02:11Z]: AADSTS70011: The provided request must include a 'scope' input parameter. The provided value for the input parameter 'scope' is not valid. The scope api://<azureAppId>/.default Files.Read openid profile offline_access is not valid.

The only scope supported by the function is /.default, which doesn't use the permissions added in Azure and when I try to add scopes into the function code, it returns an error saying the scopes parameter is not valid.

But when I generate a token using Postman with the same credentials, it works and adds the scopes I need to access OneDrive

3 Answers

It's difficult to say exactly how to resolve your issue. We don't know what is the purpose of obtaining that token - Azure is using a combination of authentication and access tokens. Given you're defining a 'Files.Read' scope, you probably aim to authorize the user.

After looking into:

https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/2c58d3ff42a04052e16758f5496a30e76b4c39b2/lib/msal-common/docs/request.md

and personal experience, I would recommend you try to following:

  1. Clarify the purpose of the token and where would be the best place (FE/BE) to achieve that. For instance, when logging the user in or authorizing requests to your backend, best practice would be to use the msal library on the frontend (obtain the authentication token there) and hide your backend behind and API Management service, and configure its policies to authorize requests based on access token - also generated on the frontend.

  2. Make sure your application (Web API) is registered in Active Directory. The Azure Documentation starts here: https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-create-new-tenant

  3. Add relevant scope to your registration: https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-configure-app-expose-web-apis

  4. In your code, start by using a single value for your scopes e.g. Mail.Read and test that (i.e. obtain the token) first before experimenting with multiple scopes.

Unfortunately I don't have experience working with OneDrive programmatically, but regardless I've done some digging into this.

According to MS documentation 'Files.Read' is a valid scope.

The application you're developing is called a daemon, service account or back-end service by MS (thanks MS for making our lives simple). In other words it is a server application which wants to authenticate with Azure without user interaction. See this diagram for reference.

Organise your code into functions or classes to avoid things like using return statement outside of function / method body.

Refer to this example to call the MS Graph API to authenticate and authorise your application. This should allow you to get the access token.

Once you're able to get the token, refer to this walkthrough of OneDrive REST API for details on how to programmatically access it and interact with it.

PS A general advice on working with MS Azure documentation: don't count on finding a page that gives you an exact recipe for what you're trying to achieve (though you might be sometimes lucky). Azure is just too big. Try to get a high level understanding of the steps that need to happen - diagrams are usually helpful. Then, just dig around to find pages on each of the steps for your specific use case.

I don't know what you have in the variable that you use for your request: ${config.azureAppId}/.default, but it does not sound right. You should not need to specify you app ID in the scope. In fact, just '/.default' should be sufficient. But, if you want to be more verbose, I believe it should be https://graph.microsoft.com/.default.

You second approach has the problem that you are trying to use a specific scope instead of just the default scope. This article describes well why you can / have to only use the default scope with services like this one.

In short: In a scenario where you're using a user's account to perform some operation, that user has to consent to your app having the rights to perform said operation. In your scenario, no user exists who could consent to anything. Therefore, an admin has to give his consent beforehand, and then your service is quite ready to do everything, without further need for granular scopes.

Related