How to acquire access token using client secret flow with app only scope? (using msal4j)

Matt Mazzola 6 Reputation points Microsoft Employee
2022-05-03T23:55:44.55+00:00

Hi, I am trying to acquire a token using a client secret with Java.

I have already done the setup steps of registering the app in AAD and have updated other apps with the ability to sign-in / authenticate users using @azure/msal-browser and made requests to Graph API so I know the app is configured correctly.

I have another confidential client that is written in Java and I would like for it to also be able to acquire access tokens to access protected resources.

I see the samples listed on the documentation here:
https://learn.microsoft.com/en-us/azure/active-directory/develop/sample-v2-code#web-api

I have written a sample app based on these two code samples:

https://github.dev/Azure-Samples/ms-identity-java-daemon/blob/master/msal-client-credential-secret/src/main/java/ClientCredentialGrant.java
https://github.com/AzureAD/microsoft-authentication-library-for-java/blob/dev/src/samples/confidential-client/ClientCredentialGrant.java

The app does the following:

var authority = "https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47";
var clientId = "<removed>";
var clientSecret = "<removed>";
var scope = "api://<clientId>/<appName>.Read.All/.default";

var clientApplication = ConfidentialClientApplication.builder(clientId, ClientCredentialFactory.createFromSecret(clientSecret))
.authority(authority)
.build();

var clientCredentialParam = ClientCredentialParameters.builder(Collections.singleton(scope))
.build();

var future = clientApplication.acquireToken(clientCredentialParam);
var accessToken = future.get();

This is about as basic minimal usage of the msal4j as you could get and yet I still get the following error:
Exception in thread "main" java.util.concurrent.ExecutionException: com.microsoft.aad.msal4j.MsalServiceException: AADSTS500011: The resource principal named api://<removed> was not found in the tenant named Microsoft. 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 might have sent your authentication request to the wrong tenant.

I have verified in the Azure portal that nothing in this app requires admin consent. I also have been able to acquire access tokens to this same scope from a different app using user auth from public client so I know the scope exists. (Although perhaps the url / value is still wrong in Java)

It seems to be using the "scope" parameter as the "clientId" and complaining that the scope doesn't exist as an app/principal in the tenant 72f98/Microsoft.
I assume this means I have somehow configured this sample wrong, but I given there are only 4 parameters (authority, clientId, clientScope, scope) I don't see what I could change.

What is the correct configuration to use this client-secret grant?
Perhaps I have the authority configured wrong?

Microsoft Entra ID
Microsoft Entra ID
A Microsoft Entra identity service that provides identity management and access control capabilities. Replaces Azure Active Directory.
19,457 questions
{count} vote

1 answer

Sort by: Most helpful
  1. Matt Mazzola 6 Reputation points Microsoft Employee
    2022-05-09T23:46:52.93+00:00

    Ok, I think I've found solution that should work for the cases where you are administrator and can give yourself consent. If you can't grant consent I think you'll be stuck for daemon style applications. They would be able to acquire a token, but that token won't have any permissions.

    I did some more experimenting and created a new application on my personal tenant where I could give consent and it worked so I can resolve this issue since it was originally about how to get acquire token using client credentials for an application scope. The issue for my case has shifted to now be about consent which is different.

    (I'm likely going to delete this app registration and create a new one in different tenant so I don't mind leaving client id guids since they aren't secrets)

    Notes:

    1. The code above is correct so you can copy it and use with your different configuration values (authority, clientId, clientSecret)
    2. The authority must use the actual tenant id and not "common"
    3. For client credentials the scope must be full URL and must be in the form: api://7cf8f145-b6e2-403b-9d42-e9f99f864016/.default

    There were multiple issues so I will summarize them:

    1. If you use https://login.microsoftonline.com/common in your authority (the URL you would use for delegated user access) but clientId correct the error message is: "Specified tenant identifier '7cf8f145-b6e2-403b-9d42-e9f99f864016' is neither a valid DNS name, nor a valid external domain." This was confusing to me because it seems to be using the client id as the tenant id, which did not make sense.

    2. The scope must be form of api://<resourceId>/.default This was the issue that spend the most time on.
    When using delegated permission, I would use the URL from this area in Azure Portal as the value for the scope 200462-image.png Then when creating new Application permission I thought URL in similar position was also the scope 200453-image.png

    I then saw the documentation say &#34;scope={resource}/.default&#34; and thought the /.default was simply ADDED to existing resource.
    

    This would make it: Incorrect: api://7cf8f145-b6e2-403b-9d42-e9f99f864016/Task.Write/.default

    The /.default actually REPLACES the existing name Correct: api://7cf8f145-b6e2-403b-9d42-e9f99f864016/.default

    When you acquire token with delegated user permissions, such as to graph api, the scope will be added to the token with scp claim, resulting in something like this:

    &#34;scp&#34;: &#34;openid profile User.Read email&#34;,
    

    When you acquire token using app permission the roles will be added to the token in roles claim resulting in something like this:

    {
      ....
      &#34;roles&#34;: [
        &#34;Task.Write&#34;,
      ],
      ...
    }
    

    Feedback:

    1. There are some improvements that could be made to the UI and Azure portal to make thing clearer
    2. The error message when you have common in authority but attempting client credentials grant could be clearer
    3. I think the API can be confusing because values used for certain terms is not consistent For example, when specifying scopes I had done "user" authentication before and scopes requested were always short strings like "openid profile User.Read email" However, when the same term "scope" is used event for ConfidentialClient where the value you put is actually the entire resource identifier URI
    4. The weird quirk about using /.default seems unnecessary. If this is truly a default then why isn't it assumed by AAD and remove the complexity from user / config.
    0 comments No comments