Daemon app that calls web APIs - acquire a token

After you've constructed a confidential client application, you can acquire a token for the app by calling AcquireTokenForClient, passing the scope, and optionally forcing 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 Active Directory (Azure AD) to use the application-level permissions declared statically during application registration. Also, these API permissions must be granted by a tenant administrator.

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

Azure AD (v1.0) resources

The scope used for client credentials should always be the resource ID followed by /.default.


When MSAL requests an access token for a resource that accepts a version 1.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. So if, like Azure SQL Database (https://database.windows.net), the resource expects an audience that ends with a slash (for Azure SQL Database, 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 its equivalent, depending on the platform.

using Microsoft.Identity.Client;

// With client credentials flows, the scope is always of the shape "resource/.default" because 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 doesn't have sufficient permissions.
    // - Did you declare enough app permissions during app creation?
    // - Did the tenant admin grant permissions to the application?
catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011"))
    // Invalid scope. The scope has to be in the form "https://resourceurl/.default"
    // Mitigation: Change the scope to be as expected.


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

First case: Access the token request by using 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 the token request by using 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 you call AcquireTokenForClient, because 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 Insufficient privileges to complete the operation error when you call the API, the tenant administrator needs to grant permissions to the application. See step 6 of Register the client app above. You'll typically see an error that looks like this error:

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