最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

How to make delegated permissions work without turning on Allow public client flows on the Azure App - Stack Overflow

programmeradmin4浏览0评论

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
  • 1 Using username/password authentication, i.e. ROPC flow is a security risk. Do you have a particular reason for using it? Any reason not to use interactive authentication + refresh tokens? – juunas Commented Mar 24 at 18:24
  • If you don't want to use Allow public client flow and wants to use delegated type flow where user-interaction is involved, why can't you use authorization_code flow? @Avinash Reddy – Pratik Jadhav Commented Mar 25 at 5:00
  • @juunas We were using ClientCredentials but client wanted to use delegated permissions. And also i am using azure identity. Can we pass client secret with Username Password authentication. – Avinash Reddy Commented Mar 25 at 9:22
  • @PratikJadhav I am not aware of authroization_code flow. Can you provide me a reference of how can we implement it in java – Avinash Reddy Commented Mar 25 at 9:28
  • Your aim is to generate the access token with scope Group,Member.Read.All with delegated type authorization code flow? – Pratik Jadhav Commented Mar 25 at 9:32
 |  Show 2 more comments

1 Answer 1

Reset to default 1

I 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

与本文相关的文章

发布评论

评论列表(0)

  1. 暂无评论