In my previous article , I showed you how to modify our great Graph Client for Java sample to add some additional options for things like filtering, setting the max retries for 429 errors, etc.  That sample uses the Oauth2 Device Code flow.  In this article, I will show you how to convert that and use the Client Credentials Flow.  Although, you will not be able to retrieve the same information as in the previous example, since the client credentials relies on application permissions instead of delegated permissions ( see this great article by Bac Hoang for the differences between the two ). I have modified it to show some examples using this flow.  If you haven’t already downloaded and gotten the sample project working, please do so here.

Update the App Registration

The first step you will need to make is go to the azure portal and to the app registration you created for the sample app.  You will need to add a client secret to the app registration like so:

Be sure to copy the secret that is generated and store that somewhere secure as you will not be able to retrieve that later if you don’t ( see how my value is masked with ****** ). Next, we need to add some application permissions to the app registration.  For this change, let’s add the Microsoft Graph Application Permission “User.Read.All” and “Calendars.ReadWrite”.  You can remove the delegated permissions if you like or leave them if you want the previous sample to still work.  Be sure to provide Admin Consent.

Now, back to the project to make our code changes…

Modify the java code

You will need to modify the file src\main\resources\com\contoso\oAuth.properties to change the scopes value ( I just commented out my old version ) and add the client secret.  For this, I am using the short hand notation of /.default for the Microsoft Graph endpoint:  https://graph.microsoft.com/.default otherwise, I would have to list each one separately like so:  https://graph.microsoft.com/Calendars.ReadWrite https://graph.microsoft.com/User.Read.All
As you can see, it is easier to just use /.default and only have to write the resource one time.

Next, let’s modify the file Graph.java under src\mail\java\com\contoso

Add the import: import com.microsoft.graph.models.extensions.Calendar;  at the top.

Let’s modify the method getUser.  We will add an additional parameter to specify the user since we are doing a client credentials flow, there is no user context so we need this method to look up a user based on a User Principal Name (upn) we are going to pass in.  You can see where I just commented out the old “me” code:

    public static User getUser(String accessToken, String upn) {
        ensureGraphClient(accessToken);

        // GET /me to get authenticated user
        /*
        User me = graphClient
            .me()
            .buildRequest()
            .get();
        */

        try{
            User user = graphClient
                .users(upn)
                .buildRequest()
                .get();
            return user;       
        } catch ( Exception ex ) {
            System.out.println("Error getting user " + ex.getMessage());
            return null;
        }
    }

We will need to create a GetCalendar method:

    public static Calendar GetCalendar(String accessToken, String upn){
        ensureGraphClient(accessToken);
        try {
            Calendar cal = graphClient
                .users(upn)
                .calendar()
                .buildRequest()
                .get();
            return cal;
        } catch (Exception ex){
            System.out.println("Error getting calendar " + ex.getMessage());
            return null;
        }
    }

Next, let’s modify Authentication.java to use the new client credentials.

Some new imports that need to be added:
import com.microsoft.aad.msal4j.ClientCredentialFactory;
import com.microsoft.aad.msal4j.ClientCredentialParameters;
import com.microsoft.aad.msal4j.ConfidentialClientApplication;
import com.microsoft.aad.msal4j.IClientCredential;
import com.microsoft.aad.msal4j.SilentParameters;
import java.net.MalformedURLException;

In public class Authentication, add a new static variable:  clientSecret

    private static String applicationId;
    // Set authority to allow only organizational accounts
    // Device code flow only supports organizational accounts
    private static String authority;
    private static String clientSecret;

Modify the initialize method like so:

    public static void initialize(String applicationId, String authority, String clientSecret) {
        Authentication.authority = authority;
        Authentication.applicationId = applicationId;
        Authentication.clientSecret = clientSecret;
    }

You can next comment out all of the devicecode flow code like so:

        /*
        PublicClientApplication app;
        try {
            // Build the MSAL application object with
            // app ID and authority
            app = PublicClientApplication.builder(applicationId)
                .authority(authority)
                .build();
        } catch (MalformedURLException e) {
            return null;
        }

        
        // Create consumer to receive the DeviceCode object
        // This method gets executed during the flow and provides
        // the URL the user logs into and the device code to enter
        Consumer<DeviceCode> deviceCodeConsumer = (DeviceCode deviceCode) -> {
            // Print the login information to the console
            System.out.println(deviceCode.message());
        };

        // Request a token, passing the requested permission scopes
        IAuthenticationResult result = app.acquireToken(
            DeviceCodeFlowParameters
                .builder(scopeSet, deviceCodeConsumer)
                .build()
        ).exceptionally(ex -> {
            System.out.println("Unable to authenticate - " + ex.getMessage());
            return null;
        }).join();
        */

Then, below that comment block but before the if(result != null) block, add this code:

		IClientCredential cred = ClientCredentialFactory.createFromSecret(clientSecret);
        ConfidentialClientApplication app;
        try {
            // Build the MSAL application object for a client credential flow
            app = ConfidentialClientApplication.builder(applicationId, cred ).authority(authority).build();
        } catch (MalformedURLException e) {
            System.out.println("Error creating confidential client: " + e.getMessage());
            return null;
        }
        
        IAuthenticationResult result;
        try{
            SilentParameters silentParameters = SilentParameters.builder(scopeSet).build();
            result= app.acquireTokenSilently(silentParameters).join();
        } catch (Exception ex ){
            if (ex.getCause() instanceof MsalException) {

                ClientCredentialParameters parameters =
                        ClientCredentialParameters
                                .builder(scopeSet)
                                .build();
   
                // Try to acquire a token. If successful, you should see
                // the token information printed out to console
                result = app.acquireToken(parameters).join();
            } else {
                // Handle other exceptions accordingly
                System.out.println("Unable to authenticate = " + ex.getMessage());
                return null;
            }
        }

My Full file looks like this:

package com.contoso;

import java.net.MalformedURLException;
// import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;

import javax.security.auth.login.Configuration.Parameters;

import com.microsoft.aad.msal4j.AuthorizationCodeParameters;
import com.microsoft.aad.msal4j.ClientCredentialFactory;
import com.microsoft.aad.msal4j.ClientCredentialParameters;
import com.microsoft.aad.msal4j.ConfidentialClientApplication;
import com.microsoft.aad.msal4j.DeviceCode;
import com.microsoft.aad.msal4j.DeviceCodeFlowParameters;
import com.microsoft.aad.msal4j.IAuthenticationResult;
import com.microsoft.aad.msal4j.IClientCredential;
import com.microsoft.aad.msal4j.MsalException;
import com.microsoft.aad.msal4j.PublicClientApplication;
import com.microsoft.aad.msal4j.SilentParameters;


/**
 * Authentication
 */
public class Authentication {

    private static String applicationId;
    // Set authority to allow only organizational accounts
    // Device code flow only supports organizational accounts
    private static String authority;
    private static String clientSecret;

    public static void initialize(String applicationId, String authority, String clientSecret) {
        Authentication.authority = authority;
        Authentication.applicationId = applicationId;
        Authentication.clientSecret = clientSecret;
    }

    public static String getUserAccessToken(String[] scopes) {
        if (applicationId == null) {
            System.out.println("You must initialize Authentication before calling getUserAccessToken");
            return null;
        }

         Set<String> scopeSet = new HashSet<>();
        
        Collections.addAll(scopeSet, scopes);
        /*
        PublicClientApplication app;
        try {
            // Build the MSAL application object with
            // app ID and authority
            app = PublicClientApplication.builder(applicationId)
                .authority(authority)
                .build();
        } catch (MalformedURLException e) {
            return null;
        }

        
        // Create consumer to receive the DeviceCode object
        // This method gets executed during the flow and provides
        // the URL the user logs into and the device code to enter
        Consumer<DeviceCode> deviceCodeConsumer = (DeviceCode deviceCode) -> {
            // Print the login information to the console
            System.out.println(deviceCode.message());
        };

        // Request a token, passing the requested permission scopes
        IAuthenticationResult result = app.acquireToken(
            DeviceCodeFlowParameters
                .builder(scopeSet, deviceCodeConsumer)
                .build()
        ).exceptionally(ex -> {
            System.out.println("Unable to authenticate - " + ex.getMessage());
            return null;
        }).join();
        */

        
        IClientCredential cred = ClientCredentialFactory.createFromSecret(clientSecret);
        ConfidentialClientApplication app;
        try {
            // Build the MSAL application object for a client credential flow
            app = ConfidentialClientApplication.builder(applicationId, cred ).authority(authority).build();
        } catch (MalformedURLException e) {
            System.out.println("Error creating confidential client: " + e.getMessage());
            return null;
        }
        
        IAuthenticationResult result;
        try{
            SilentParameters silentParameters = SilentParameters.builder(scopeSet).build();
            result= app.acquireTokenSilently(silentParameters).join();
        } catch (Exception ex ){
            if (ex.getCause() instanceof MsalException) {

                ClientCredentialParameters parameters =
                        ClientCredentialParameters
                                .builder(scopeSet)
                                .build();
   
                // Try to acquire a token. If successful, you should see
                // the token information printed out to console
                result = app.acquireToken(parameters).join();
            } else {
                // Handle other exceptions accordingly
                System.out.println("Unable to authenticate = " + ex.getMessage());
                return null;
            }
        }


        if (result != null) {
            // System.out.println("Access Token - " + result.accessToken());
            return result.accessToken();
        }

        return null;
    }
}

Finally, let’s modify the App.java file located in the same folder.

You will need to add a final String for the clientSecret like so:
        final String clientSecret = oAuthProperties.getProperty(“app.clientSecret”);

Then, for authentication to occur, you can initialize authentication with this block:

        Authentication.initialize(appId, authority, clientSecret);
        final String accessToken = Authentication.getUserAccessToken(appScopes);         System.out.println(“Access token = ” + accessToken

The rest of the code is to add some additional functionality to work with the new permissions we have setup.  I have added a promptForUPN method so we can change the upn of the user we are working with and a couple of new choice options in the menu.  The full file is here:

package com.contoso;

import java.util.InputMismatchException;
import java.util.Scanner;

import com.microsoft.graph.models.extensions.DateTimeTimeZone;
import com.microsoft.graph.models.extensions.Event;
import com.microsoft.graph.models.extensions.User;
import com.microsoft.graph.models.extensions.Calendar;

import java.io.Console;
import java.io.IOException;
import java.util.Properties;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.List;

/**
 * Graph Tutorial
 *
 */
public class App {
    public static void main(String[] args) {
        System.out.println("Java Graph Tutorial");
        System.out.println();

        // Load OAuth settings
        final Properties oAuthProperties = new Properties();
        try {
            oAuthProperties.load(App.class.getResourceAsStream("oAuth.properties"));
        } catch (IOException e) {
            System.out.println("Unable to read OAuth configuration. Make sure you have a properly formatted oAuth.properties file. See README for details.");
            return;
        }

        final String appId = oAuthProperties.getProperty("app.id");
        final String[] appScopes = oAuthProperties.getProperty("app.scopes").split(",");
        final String authority = oAuthProperties.getProperty("app.authority");
        final String clientSecret = oAuthProperties.getProperty("app.clientSecret");

        // Get an access token
        //Authentication.initialize(appId, authority);j
        Authentication.initialize(appId, authority, clientSecret);
        final String accessToken = Authentication.getUserAccessToken(appScopes);
        System.out.println("Access token = " + accessToken);
        
        String upn = promptForUPN();
        
        // Greet the user
        User user = Graph.getUser(accessToken,upn);
        if(user!=null){
            System.out.println("You have select user " + user.displayName);
        }
        
        Scanner input = new Scanner(System.in);

        int choice = -1;

        while (choice != 0) {
            System.out.println();
            System.out.println("Please choose one of the following options:");
            System.out.println("0. Exit");
            System.out.println("1. Display access token");
            System.out.println("2. Input upn to work with");
            System.out.println("3. Get this users info");
            System.out.println("4. Get this users calender");

            try {
                choice = input.nextInt();
            } catch (InputMismatchException ex) {
                // Skip over non-integer input
                input.nextLine();
            }

            // Process user choice
            switch(choice) {
                case 0:
                    // Exit the program
                    System.out.println("Goodbye...");
                    break;
                case 1:
                    // Display access token
                    System.out.println("Access token: " + accessToken);
                    break;
                case 2:
                    upn = promptForUPN();
        
                    // Greet the user
                    user = Graph.getUser(accessToken,upn);
                    if(user!=null){
                        System.out.println("You have selected user " + user.displayName);
                    }
                    break;

                case 3:
                    if(user!=null){
                        System.out.println("User info:");
                        System.out.println("    id= " + user.id);
                        System.out.println("    mail= " + user.mail);
                    } else {
                        System.out.println("*** No user selected ***");
                    }
                    break;
                case 4:
                    if(user!=null){
                        Calendar cal = Graph.GetCalendar(accessToken,upn);
                        System.out.println("Calendar info:");
                        System.out.println("    id= " + cal.id );                        
                    } else {
                        System.out.println("*** No user selected ***");
                    }
                    break;
                default:
                    System.out.println("Invalid choice");
            }
        }

        input.close();
    }

    private static String formatDateTimeTimeZone(DateTimeTimeZone date) {
        LocalDateTime dateTime = LocalDateTime.parse(date.dateTime);
    
        return dateTime.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)) + " (" + date.timeZone + ")";
    }

    private static String promptForUPN(){
        String upn;
        Console console = System.console();
        upn = console.readLine("Enter user upn: ");
        return upn;
    }
  }

Reference for the client credentials provider for java:  https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-daemon-acquire-token?tabs=java#acquiretokenforclient-api

Run the code

You will notice now when you run the project, you are not prompted for any kind of authentication.  Instead, right away, you will see the access token come back in the console window, and then we will be prompted to enter a upn so we can get details about this user:

Looking at the access token in http://jwt.ms you can see we have an application only token for Microsoft graph:

After the access token gets displayed and the initial user information for the upn you entered, you will get the menu of actions you can use for this app, including being prompted for a new upn if desired.

Press 3 to get this users info and you will get the id and mail attribute ( if set ).

Summary

As you can see, we are now able to look up users using the client credentials flow.  This is only recommended for scenarios where the application is running in a secure environment.  I do not recommend this flow being used in any kind of distributed application as the client secret is easily retrieved.  This should be server side scripts that users do not have access to.  Also, be careful when assigning application permissions because you can potentially give the application far more permissions than is needed which may put you in a compromised state.

22 Thoughts to “Implement Client Credentials flow for Graph Java Client”

  1. Ray

    Hi Sir, I am able to get the token but I am not able to get the user. I get the error com.microsoft.graph.http.GraphServiceException: Error code: Request_ResourceNotFound
    Can you please help?

    1. Ray Held [MSFT]

      A great way to test this is to perform the query in the Graph Explorer tool: https://developer.microsoft.com/en-us/graph/graph-explorer You will need to sign-in with the “Sign in with Microsoft” button on the left side of the page and for the user endpoint, you will need User.Read.All permission. But, if you cannot find the user in this tool, then that means that the user you’re searching for is not found. Documentation for the user endpoint can be found here: https://docs.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http A common mistake is a developer searching a upn that is a MSA ( Microsoft account ) such as a hotmail, msn, etc. If that user was invited with that email address to the tenant, then a unique UPN was generated for that user and it is not the same as the email address that was used.

  2. Anand

    Hi Sir,

    As I am trying to get the token but getting below exception, what code need to change, can you please suggest. I am using msal4j-1.9.1.jar.

    [ForkJoinPool.commonPool-worker-1] ERROR com.microsoft.aad.msal4j.ConfidentialClientApplication – [Correlation ID: “GETTING_SOME_ID”] 
    Execution of class com.microsoft.aad.msal4j.AcquireTokenSilentSupplier failed.
    com.microsoft.aad.msal4j.MsalClientException: java.net.SocketTimeoutException: connect timed out

    Regards,
    Anand

    1. Bac Hoang [MSFT]

      Please check your network connectivity issue. This error means that the client app is not able to set up an SSL connection to the endpoint. You may want to look at wireshark trace to see why the app can’t set up SSL connection to Azure AD’s login endpoint: login.microsoftonline.com. Check if there is any firewall or proxy in your environment that might have blocked outbound connection. The error cause could be due to the machine not having an updated SSL certificate info from Azure AD’s endpoint.

      1. anand

        Hi Sir, I have solved this issue setting system.setProperty “http.proxyHost” and “http.proxyPort” now I am getting accessToken. But now I am getting below error
        while adding the event into client using

        graphClient.me().events().buildRequest().post(newEvent);

        SEVERE: Throwable detail: com.microsoft.graph.core.ClientException: Error during http request
        com.microsoft.graph.core.ClientException: Error during http request
        at com.microsoft.graph.http.CoreHttpProvider.sendRequestInternal(CoreHttpProvider.java:471)
        at com.microsoft.graph.http.CoreHttpProvider.send(CoreHttpProvider.java:220)
        at com.microsoft.graph.http.CoreHttpProvider.send(CoreHttpProvider.java:200)
        at com.microsoft.graph.http.BaseRequest.send(BaseRequest.java:345)
        at com.microsoft.graph.requests.extensions.EventRequest.post(EventRequest.java:136)
        at com.microsoft.graph.requests.extensions.EventCollectionRequest.post(EventCollectionRequest.java:76)
        at com.ms.eandc.etool.REST.client.RESTWebServicesClient.createEvent(RESTWebServicesClient.java:84)
        at com.ms.eandc.etool.REST.client.AddRemoveOffice365EventCalendar.addCalendar(AddRemoveOffice365EventCalendar.java:102)
        at com.ms.eandc.etool.common.email.Office365Thread.run(Office365Thread.java:29)
        Caused by: java.io.IOException: Failed to authenticate with proxy

        Please suggest.

        Thank you

  3. rajnikant

    Hi,
    I am able to connect and get the token. I want to read the mails and download as .eml how we can do this also after download the mails moving to another folder. using Java.

    1. Ray Held [MSFT]

      Hi rajnikant, you cannot do that with Graph as graph only returns JSON format data.

    2. Diva

      Rajni, Are you able to connect and read the Email?. I am trying to connect to an outlook Email (office 365) and read a Shared Email InBox. It worked four years ago. Now I am trying the same using Oauth2. Any Idea?
      Thanks
      Diva (diva.nepurayil@doas.ga.gov)

  4. Mansi

    Could you please share the JDK and Gradle version for which above code works? I am using JDK11 and facing Java.lang.NoClassDefFoundError: com/nimbusds/oauth2/sdk/auth/ClientAuthentication

    1. Bac Hoang [MSFT]

      This was based on the documentation at https://docs.microsoft.com/en-us/graph/tutorials/java. You can take a look at that link for versioning info.

  5. Pawan

    Hi,
    I’m trying to get the access token from Azure AD but got this error:
    Caused by: com.microsoft.aad.msal4j.MsalClientException: java.net.SocketTimeoutException: connect timed out
    at com.microsoft.aad.msal4j.HttpHelper.executeHttpRequest(HttpHelper.java:53)
    at com.microsoft.aad.msal4j.AadInstanceDiscoveryProvider.executeRequest(AadInstanceDiscoveryProvider.java:218)
    at com.microsoft.aad.msal4j.AadInstanceDiscoveryProvider.sendInstanceDiscoveryRequest(AadInstanceDiscoveryProvider.java:172)
    at com.microsoft.aad.msal4j.AadInstanceDiscoveryProvider.doInstanceDiscoveryAndCache(AadInstanceDiscoveryProvider.java:271)
    at com.microsoft.aad.msal4j.AadInstanceDiscoveryProvider.getMetadataEntry(AadInstanceDiscoveryProvider.java:56)
    at com.microsoft.aad.msal4j.AuthenticationResultSupplier.getAuthorityWithPrefNetworkHost(AuthenticationResultSupplier.java:32)
    at com.microsoft.aad.msal4j.AcquireTokenByAuthorizationGrantSupplier.execute(AcquireTokenByAuthorizationGrantSupplier.java:59)
    at com.microsoft.aad.msal4j.AcquireTokenByClientCredentialSupplier.acquireTokenByClientCredential(AcquireTokenByClientCredentialSupplier.java:63)
    at com.microsoft.aad.msal4j.AcquireTokenByClientCredentialSupplier.execute(AcquireTokenByClientCredentialSupplier.java:49)
    at com.microsoft.aad.msal4j.AuthenticationResultSupplier.get(AuthenticationResultSupplier.java:59)
    … 7 more
    Any help will be appreciated. I’ve tested it with both condition i.e. with proxy and without proxy.

    1. Bac Hoang [MSFT]

      this error is an environmental issue. SocketTimeoutException generally means “destination unreachable”. This error usually occurs when the client application is not able to create a secure (SSL) connection to the endpoint (login.microsoftonline.com in this case). There is something in the network (proxy, firewall, SSL version mismatched, etc..) blocking the connection. You want to work with your IT admin to see why the endpoint is blocked.

  6. Mahendra

    Hi Ray,

    Where can I find the implementation for the ensureGraphClient(accessToken)? I can get the request token using MSAL4J. I am struggling with getting a GraphClient from that token.

    1. Vrush

      Hi Mahendra Did u find how to create graph client using IAuthenticationResult result token(MSAL4J)

  7. Raghunath Mukherjee

    Hi Sir , I am able to get the token but do not see the function to access graphClient. There is function call to ensureGraphClient(accessToken), but the function itself do not exist in sample tutorial https://docs.microsoft.com/en-us/graph/tutorials/java?tabs=aad.

    Also to access the function Graph.getInbox() Mail.ReadWrite and User.Read permissions are enough ?

    1. Ray Held [MSFT]

      That is part of the tutorial I had referred to in the previous post. You can find that tutorial here: https://docs.microsoft.com/en-us/graph/tutorials/java?tabs=aad

  8. Mahendra

    Hi Can you share the code for ensureGraphClient(accessToken);?

    1. Ray Held [MSFT]

      That is part of the tutorial I had referred to in the previous post. You can find that tutorial here: https://docs.microsoft.com/en-us/graph/tutorials/java?tabs=aad

      1. Mahendra

        I used both the examples you provided. The other tutorial is using com.microsoft.graph API’s and that one throws random error “Error getting user Error code: InvalidAuthenticationToken
        Error message: Continuous access evaluation resulted in claims challenge with result: InteractionRequired and code: LocationConditionEvaluationSatisfied”. It works sometimes and fails. At the same time com.microsoft.aad.msal4j API’s is returning token and working fine. Based on your tutorial here, I thought you can use the MSAL4J API to get the token and then can create the graphclient using that token string. I do not see any API to create a graphclient using string token. How do I do that?

        1. Vrush

          Hi mahendra Did u find a way to create graph client using token received from iauthenticationresult (msal)

          1. Ray Held [MSFT]

            I have gone through the tutorial for the MS Graph Java client — they have added the client credentials flow. There are some issues with the sample that I resolved and uploaded my entire project to my GitHub. Tutorial I walked through: https://docs.microsoft.com/en-us/graph/tutorials/java?tabs=aad
            My Project: https://github.com/RayGHeld/Graph_Java_Sample

      2. Mahendra

        Hi Ray,

        Perhaps the link you are refering to is updated. The code on that link does not have a function ensureGraphClient(accessToken).

        Mahendra

Leave a Reply to Ray Cancel reply