Daemon app that calls web APIs - acquire a token

Once the confidential client application is constructed, you can acquire a token for the app by calling AcquireTokenForClient, passing the scope, and forcing or not a refresh of the token.

Scopes to request

The scope to request for a client credential flow is the name of the resource followed by /.default. This notation tells Azure AD to use the application level permissions declared statically during the application registration. Also, as seen previously, these API permissions must be granted by a tenant administrator

ResourceId = "someAppIDURI";
var scopes = new [] {  ResourceId+"/.default"};

Case of Azure AD (v1.0) resources

The scope used for client credentials should always be resourceId+"/.default"


For MSAL asking an access token for a resource accepting a v1.0 access token, Azure AD parses the desired audience from the requested scope by taking everything before the last slash and using it as the resource identifier. Therefore if, like Azure SQL (https://database.windows.net) the resource expects an audience ending with a slash (for Azure SQL: https://database.windows.net/), you'll need to request a scope of https://database.windows.net//.default (note the double slash). See also MSAL.NET issue #747: Resource url's trailing slash is omitted, which caused sql auth failure.

AcquireTokenForClient API

To acquire a token for the app, you'll use AcquireTokenForClient or the equivalent depending on the platforms.

using Microsoft.Identity.Client;

// With client credentials flows the scopes is ALWAYS of the shape "resource/.default", as the
// application permissions need to be set statically (in the portal or by PowerShell), and then granted by
// a tenant administrator
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };

AuthenticationResult result = null;
 result = await app.AcquireTokenForClient(scopes)
catch (MsalUiRequiredException ex)
    // The application does not have sufficient permissions
    // - did you declare enough app permissions in during the app creation?
    // - did the tenant admin needs to grant permissions to the application.
catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011"))
    // Invalid scope. The scope has to be of the form "https://resourceurl/.default"
    // Mitigation: change the scope to be as expected !


If you don't have yet a library for your language of choice, you might want to use the protocol directly:

First case: Access token request with a shared secret

POST /{tenant}/oauth2/v2.0/token HTTP/1.1           //Line breaks for clarity
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded


Second case: Access token request with a certificate

POST /{tenant}/oauth2/v2.0/token HTTP/1.1               // Line breaks for clarity
Host: login.microsoftonline.com
Content-Type: application/x-www-form-urlencoded

&client_assertion=eyJhbGciOiJSUzI1NiIsIng1dCI6Imd4OHRHeXN5amNScUtqRlBuZDdSRnd2d1pJMCJ9.eyJ{a lot of characters here}M8U3bSUKKJDEg

For more information, see the protocol documentation: Microsoft identity platform and the OAuth 2.0 client credentials flow.

Application token cache

In MSAL.NET, AcquireTokenForClient uses the application token cache (All the other AcquireTokenXX methods use the user token cache) Don't call AcquireTokenSilent before calling AcquireTokenForClient as AcquireTokenSilent uses the user token cache. AcquireTokenForClient checks the application token cache itself and updates it.


Did you use the resource/.default scope?

If you get an error message telling you that you used an invalid scope, you probably didn't use the resource/.default scope.

If you get an error when calling the API Insufficient privileges to complete the operation, the tenant administrator needs to grant permissions to the application. See step 6 of Register the client app above. You'll typically see and error like the following error description:

Failed to call the web API: Forbidden
Content: {
  "error": {
    "code": "Authorization_RequestDenied",
    "message": "Insufficient privileges to complete the operation.",
    "innerError": {
      "request-id": "<guid>",
      "date": "<date>"

Next steps