I have an azure app with delegated permissions of User.Read.All and GroupMember.Read.All.
Following are the permissions of my app with name Azure_AD_Delegated
I have used the username and password authentication. Here is my code
new UsernamePasswordCredentialBuilder().clientId(clientId).username(username).password(password)
.tenantId(tenantId).build().getTokenSync(tokenRequestContext.addScopes((".default")));
With the above code and Allow Public Client Flows not being turned on my app i get the following exception.
com.microsoft.aad.msal4j.MsalServiceException: AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret'. Trace ID: 32eeb109-2202-443c-9a5d-56606bba0e00 Correlation ID: ab8a7cea-ee9c-4ba6-8b3d-23faaef98e94 Timestamp: 2024-05-17 13:00:05Z
Once i turn on Allow Public Client Flows on my app the authentication is successful and able to invoke the graph api's
Here is my code how i generate the token using username and password authentication.
private void generateAccessTokenByUserNameAndPassword() {
try {
TokenRequestContext tokenRequestContext = new TokenRequestContext();
accessToken = new UsernamePasswordCredentialBuilder().clientId(clientId).username(username).password(password)
.tenantId(tenantId).build().getTokenSync(tokenRequestContext.addScopes((".default")));
graphClient = getGraphClient();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("rawtypes")
private GraphServiceClient getGraphClient() {
IAuthenticationProvider provider = new IAuthenticationProvider() {
@Override
public CompletableFuture<String> getAuthorizationTokenAsync(URL requestUrl) {
return CompletableFuturepletedFuture(accessToken.getToken());
}
};
return GraphServiceClient.builder().authenticationProvider(provider).buildClient();
}
But few articles by microsoft suggest turning Allow Public Client Flows is a security risk.
Here is the article
But other suggest to turn on Allow Public client Flows . Here is the link
Now how do i make username password authentication is successful and the graph api executes without turning on Allow Public client Flows.
My client is reluctant to use Application Permissions and Client Credential Flow.
Any help suggestions i can get through the username password authentication for delegated permissions ?
I have an azure app with delegated permissions of User.Read.All and GroupMember.Read.All.
Following are the permissions of my app with name Azure_AD_Delegated
I have used the username and password authentication. Here is my code
new UsernamePasswordCredentialBuilder().clientId(clientId).username(username).password(password)
.tenantId(tenantId).build().getTokenSync(tokenRequestContext.addScopes((".default")));
With the above code and Allow Public Client Flows not being turned on my app i get the following exception.
com.microsoft.aad.msal4j.MsalServiceException: AADSTS7000218: The request body must contain the following parameter: 'client_assertion' or 'client_secret'. Trace ID: 32eeb109-2202-443c-9a5d-56606bba0e00 Correlation ID: ab8a7cea-ee9c-4ba6-8b3d-23faaef98e94 Timestamp: 2024-05-17 13:00:05Z
Once i turn on Allow Public Client Flows on my app the authentication is successful and able to invoke the graph api's
Here is my code how i generate the token using username and password authentication.
private void generateAccessTokenByUserNameAndPassword() {
try {
TokenRequestContext tokenRequestContext = new TokenRequestContext();
accessToken = new UsernamePasswordCredentialBuilder().clientId(clientId).username(username).password(password)
.tenantId(tenantId).build().getTokenSync(tokenRequestContext.addScopes((".default")));
graphClient = getGraphClient();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("rawtypes")
private GraphServiceClient getGraphClient() {
IAuthenticationProvider provider = new IAuthenticationProvider() {
@Override
public CompletableFuture<String> getAuthorizationTokenAsync(URL requestUrl) {
return CompletableFuturepletedFuture(accessToken.getToken());
}
};
return GraphServiceClient.builder().authenticationProvider(provider).buildClient();
}
But few articles by microsoft suggest turning Allow Public Client Flows is a security risk.
Here is the article
https://learn.microsoft/en-us/answers/questions/1386597/allow-public-client-flows
But other suggest to turn on Allow Public client Flows . Here is the link
https://learn.microsoft/en-us/answers/questions/1085114/delegated-access-token-without-client-secret
Now how do i make username password authentication is successful and the graph api executes without turning on Allow Public client Flows.
My client is reluctant to use Application Permissions and Client Credential Flow.
Any help suggestions i can get through the username password authentication for delegated permissions ?
Share Improve this question edited Mar 26 at 7:27 Avinash Reddy asked Mar 24 at 16:20 Avinash ReddyAvinash Reddy 2,3124 gold badges29 silver badges50 bronze badges 7 | Show 2 more comments1 Answer
Reset to default 1I agree with @juunas, username password authentication (ROPC flow) itself is a security risk and Microsoft don't recommend using it.
Alternatively, switch to authorization code flow that uses delegated permissions which is more secure and works without enabling "Allow Public Client Flows" option.
Initially, I registered one application and granted same API permissions
of Delegated type with consent as below:
In Authentication tab, I added redirect URI as http://localhost:8080/callback in Web
platform without enabling public client flow option like this:
Now, I used below sample code files to generate access token using authorization code flow and call Microsoft Graph API via Java SDK:
pom.xml:
<dependencies>
<!-- Microsoft Graph SDK -->
<dependency>
<groupId>com.microsoft.graph</groupId>
<artifactId>microsoft-graph</artifactId>
<version>5.53.0</version>
</dependency>
<!-- Azure Identity for authentication -->
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.8.2</version>
</dependency>
<!-- HTTP Server for handling OAuth Callback -->
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.9.3</version>
</dependency>
</dependencies>
Main.java:
package com.example.azureauth;
import com.azure.identity.AuthorizationCodeCredential;
import com.azure.identity.AuthorizationCodeCredentialBuilder;
import com.azure.core.credential.TokenRequestContext;
import com.microsoft.graph.authentication.IAuthenticationProvider;
import com.microsoft.graph.models.DirectoryObject;
import com.microsoft.graph.models.Group;
import com.microsoft.graph.models.User;
import com.microsoft.graph.requests.GraphServiceClient;
import com.microsoft.graph.requests.UserCollectionPage;
import com.microsoft.graph.requests.DirectoryObjectCollectionWithReferencesPage;
import okhttp3.Request;
import spark.Spark;
import java.URL;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
public class Main {
// OAuth App Credentials (Replace with actual values)
private static final String CLIENT_ID = "appId";
private static final String TENANT_ID = "tenantId";
private static final String CLIENT_SECRET = "secret_value";
private static final String REDIRECT_URI = "http://localhost:8080/callback";
private static String accessToken;
private static GraphServiceClient<Request> graphClient;
public static void main(String[] args) {
System.out.println("Azure AD OAuth2 Authorization Code Flow Starting...\n");
// Start HTTP Server for OAuth callback
Spark.port(8080);
Spark.get("/callback", (req, res) -> {
String authCode = req.queryParams("code");
if (authCode == null) {
return "Error: No authorization code found!";
}
// Exchange Authorization Code for Access Token
generateAccessTokenUsingAuthCode(authCode);
// Fetch Top 10 Users with their Groups
String userDetails = getTopUsers();
// Log Users in Console
System.out.println(userDetails);
// Return plain text user details in browser
res.type("text/plain");
return userDetails;
});
System.out.println("Server started at http://localhost:8080. Waiting for callback...\n");
}
private static void generateAccessTokenUsingAuthCode(String authorizationCode) {
try {
AuthorizationCodeCredential authCodeCredential = new AuthorizationCodeCredentialBuilder()
.clientId(CLIENT_ID)
.tenantId(TENANT_ID)
.clientSecret(CLIENT_SECRET)
.authorizationCode(authorizationCode)
.redirectUrl(REDIRECT_URI)
.build();
TokenRequestContext tokenRequestContext = new TokenRequestContext()
.addScopes("https://graph.microsoft/.default");
accessToken = authCodeCredential.getToken(tokenRequestContext).block().getToken();
System.out.println("Access Token: " + accessToken);
// Initialize Microsoft Graph API Client
graphClient = getGraphClient();
System.out.println("Access Token Retrieved Successfully\n");
} catch (Exception e) {
throw new RuntimeException("Error generating access token", e);
}
}
private static GraphServiceClient getGraphClient() {
IAuthenticationProvider provider = requestUrl -> CompletableFuturepletedFuture(accessToken);
return GraphServiceClient.builder().authenticationProvider(provider).buildClient();
}
// Fetch Top 10 Users with Groups
private static String getTopUsers() {
try {
UserCollectionPage usersPage = graphClient.users()
.buildRequest()
.select("id,displayName,mail")
.top(10)
.get();
List<User> users = usersPage.getCurrentPage();
StringBuilder userDetails = new StringBuilder();
userDetails.append("\nTOP 10 USERS WITH GROUPS\n\n");
for (User user : users) {
userDetails.append("ID : ").append(user.id).append("\n");
userDetails.append("Name : ").append(user.displayName).append("\n");
userDetails.append("Email : ").append(user.mail != null ? user.mail : "N/A").append("\n");
// Fetch groups for the user
String groups = getUserGroups(user.id);
userDetails.append("Groups : ").append(formatGroupList(groups)).append("\n");
userDetails.append("\n--------------------------------------------------------\n\n");
}
return userDetails.toString();
} catch (Exception e) {
System.err.println("Error fetching users: " + e.getMessage());
return "Error fetching users. Please try again.";
}
}
// Fetch groups the user is a member of
private static String getUserGroups(String userId) {
try {
DirectoryObjectCollectionWithReferencesPage groupsPage = graphClient
.users(userId)
.memberOf()
.buildRequest()
.get();
List<String> groupNames = groupsPage.getCurrentPage().stream()
.filter(obj -> obj instanceof Group)
.map(obj -> ((Group) obj).displayName)
.collect(Collectors.toList());
return String.join(", ", groupNames);
} catch (Exception e) {
System.err.println("Error fetching groups for user " + userId + ": " + e.getMessage());
return "No groups found";
}
}
// Format group list for better readability
private static String formatGroupList(String groups) {
final int LINE_LENGTH = 60; // Max characters per line
StringBuilder formatted = new StringBuilder();
int count = 0;
for (String group : groups.split(", ")) {
if (count + group.length() > LINE_LENGTH) {
formatted.append("\n "); // Indent new line
count = 0;
}
formatted.append(group).append(", ");
count += group.length() + 2;
}
return formatted.length() > 2 ? formatted.substring(0, formatted.length() - 2) : "No groups found";
}
}
Running the above code will start a local server at http://localhost:8080 and wait for the OAuth callback.
Now, ask the user to run below authorization request in browser and sign in with their credentials that gives access token and API response successfully:
https://login.microsoftonline/tenantId/oauth2/v2.0/authorize?
client_id=appId
&response_type=code
&redirect_uri=http://localhost:8080/callback
&scope=openid profile User.Read.All GroupMember.Read.All
&response_mode=query
&state=12345
Browser response after successful authentication:
Terminal response:
Note that, all delegated flows require some level of user interaction, meaning the user must sign in at least once to obtain a token.
The only exception is the username-password flow, where credentials are passed directly without user interaction. However, Microsoft does not recommend this method due to security concerns.
Reference:
AuthorizationCodeCredentialBuilder Class | Microsoft
Group,Member.Read.All
with delegated type authorization code flow? – Pratik Jadhav Commented Mar 25 at 9:32