2

I am learning / experimenting with Azure Active Directory B2C (AADB2C). I have created a web application that can sign-in or sign-up users (not using custom user flows) with OpenID Connect.

Once a user has signed in, I would like to make a call to Microsoft Graph to get information about the signed in user.

According to the docs, I initialize a client credential auth provider as follows:

var scopes = new[] { "https://graph.microsoft.com/.default" };

var clientSecretCredential = new ClientSecretCredential(config.TenantId, config.AppId, config.ClientSecret);

var graphClient = new GraphServiceClient(clientSecretCredential, scopes);

I then try to make a request, as follows:

var user = await graphClient.Me.Request().GetAsync();

The above results in the exception:

Code: BadRequest
Message: /me request is only valid with delegated authentication flow.

When I try the following:

var user = await graphClient.Users[ '' ].Request().GetAsync();

The above results in the exception:

Code: Authorization_RequestDenied
Message: Insufficient privileges to complete the operation.

Many of the examples in the MS documentation state that the "User.Read" permission must be granted, but when using the Azure Portal the "User.Read" permission is not found, below is a screen snapshot of the Request API Permissions blade within the Azure portal - I am still not clear on the Note (highlighted in the image below).

Azure Portal trying to add User.Read permission

When I do try to enter the "User.Read" permission, the following is displayed:

User.Read permission not found

I also found this tutorial regarding how to Register a Microsoft Graph application, but it seems to be geared to administrators who want to access the graph, not "regular" signed-in users per se.

My question - Is there any working example that illustrates how (after a user is signed in) to read the users information from Microsoft Graph?

bdcoder
  • 3,280
  • 8
  • 35
  • 55

2 Answers2

1

Note that, web applications registered with supported account type as Accounts in any identity provider or organizational directory (for authenticating users with user flows) supports only Microsoft Graph offline_access and openid delegated permissions.

I tried to reproduce the same in my environment and got below results:

I registered one application in my B2C tenant by selecting supported account type as below:

enter image description here

When I used same code to get signed-in user profile from client credentials flow, I got same error as this flow won't support Delegated permissions.

using Microsoft.Graph;
using Azure.Identity;

var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "srib2corg.onmicrosoft.com";
var clientId = "7427462d-b6e1-4ab0-ba0c-xxxxxxxx";
var clientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx";
var clientSecretCredential = new ClientSecretCredential(
                tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var user = await graphClient.Me.Request().GetAsync();

Response:

enter image description here

The following command is to fetch the list of users where I got same error as I don't have required permissions as below:

var user = await graphClient.Users["userID"].Request().GetAsync();

Response:

enter image description here

To get signed-in user profile, you need to add Delegated User.Read permission in your B2C application which is not available because of your supported account type:

enter image description here

To resolve it, you need to create an app registration with supported account type as Single tenant/Multitenant below:

enter image description here

In the above application, you can add Delegated User.Read permission like below:

enter image description here

As client credentials flow won't work with Delegated permissions, you need to change your flow to Delegated flows like Authorization code, username password etc....

In my case, I changed to Username Password flow and used below code to get user profile:

using Microsoft.Graph;
using Azure.Identity;

var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "srib2corg.onmicrosoft.com";
var clientId = "7d983e16-5296-4056-8e72-xxxxxxxx";

var options = new TokenCredentialOptions
{
    AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};

var userName = "admin@srib2corg.onmicrosoft.com";
var password = "xxxxxxxxx";

var userNamePasswordCredential = new UsernamePasswordCredential(
    userName, password, tenantId, clientId, options);

var graphClient = new GraphServiceClient(userNamePasswordCredential, scopes);

var user = await graphClient.Me.Request().GetAsync();

Console.WriteLine("\nName: " + user.DisplayName);
Console.WriteLine("\nID: " + user.Id);
Console.WriteLine("\nUPN: " + user.UserPrincipalName);

Response:

enter image description here

If your use case is to get profile after user sign in the web application, you can check below SO thread that I recently answered.

Signed-in user Profile page in ASP.NET Core Web App calling the Microsoft Graph - Stack Overflow

Sridevi
  • 10,599
  • 1
  • 4
  • 17
  • Thank-you for your detailed answer. To confirm, applications registered with the option "Accounts in any identity provider or organizational directory (for authenticating users with user flows)" cannot access Microsoft Graph as described in my question because the option supports only Microsoft Graph offline_access and openid delegated permissions - correct? It seems the only way to access Microsoft Graph in this case is with Application (Admin Consent) permissions. – bdcoder Jan 11 '23 at 15:48
  • Yes, if you want to stay with client credentials flow, you have to assign Application (Admin Consent) permissions to access Microsoft Graph – Sridevi Jan 11 '23 at 16:13
  • 1
    Thanks again for confirming. How strange, that the most common option used for apps requires us to assign application (admin consent) permission to access Microsoft Graph - which seems to go against all recommendations with regard to security. I will see if I can add application permissions and access the Graph that way. – bdcoder Jan 11 '23 at 16:23
1

In order to make calls to Microsoft Graph with an application registered with the option "Accounts in any identity provider or organizational directory (for authenticating users with user flows)", the following must be done (additional reference at: https://learn.microsoft.com/en-us/azure/active-directory-b2c/microsoft-graph-get-started?tabs=app-reg-ga):

Add the "User.ReadWrite.All" or "User.Read.All" application permission (depending on the application needs).

Note: When you add any of the above permissions via the Azure Portal, the status will initially be shown as "Not granted". Click on the line containing the permission, and then click on the "Grant admin consent for ..." button - (it is not intuitive at all), as shown below:

enter image description here

a popup window will appear to confirm, as shown below (click Yes to confirm):

enter image description here

Once confirmed, the permissions should appear as follows:

enter image description here

A few more gotcha's to be aware of:

The .Me method will not work, for example, using the following code:

var user = await graphClient.Me.Request().GetAsync();

... will still result in the exception:

Code: BadRequest
Message: /me request is only valid with delegated authentication flow.

however, you can make a request using the user ID; which may be obtained from the authenticated users claim(s), for example:

var user = await graphClient.Users[ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" ].Request().GetAsync();

The above call will work, however, be aware that some Graph properties are only returned if .Select() is used, for example, the "companyName" property is only returned if .Select is used, i.e.:

var user = await graphClient.Users[ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" ]
           .Request()
           .Select( u => new {
              u.CompanyName
           } )
           .GetAsync();

The above threw me for a loop as some properties seemed to be missing after the call when .Select() was not used, see the following for more information:

bdcoder
  • 3,280
  • 8
  • 35
  • 55