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.
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?
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.
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
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.
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
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.
Hi rajnikant, you cannot do that with Graph as graph only returns JSON format data.
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)
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
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.
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.
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.
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.Hi Mahendra Did u find how to create graph client using IAuthenticationResult result token(MSAL4J)
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 ?
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
Hi Can you share the code for
ensureGraphClient(accessToken);?
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
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?
Hi mahendra Did u find a way to create graph client using token received from iauthenticationresult (msal)
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
Hi Ray,
Perhaps the link you are refering to is updated. The code on that link does not have a function
ensureGraphClient(accessToken).
Mahendra