Roberto
Roberto

Reputation: 63

How can I retrieve my Custom User Attributes (Claims) from B2C using Graph API?

I have successfully created an application that uses a new B2C Tenant for authentication and I can login and call APIs.

I have added new Attributes for the Users (Roles one of them). In my "User Flows" I have 1 flow for SignIn that has selected the Roles claim one to login and one to edit users. In the following images you can see the setup and the returned claims, after editing with the User Flow.

Added extended user attribute

Selected to return data as claim

Decoded Token with extension claims and values

However, when I query Graph PI for the users + extensions, the extensions are not returned.

I have also access to the users using Graph API and I'm able to add extensions to the users by code using the Graph API. I can Add extended attributes, update them, retrieve them... In the code below I can Add them. These created with the Graph API I can retrieve later, but not the ones that I created with the B2C User Flow.

    // Add extension without the guid from b2c_extensions_app        
    var extension = new OpenTypeExtension()
    {
        ExtensionName = $"extension_Roles",
        AdditionalData = new Dictionary<string, object>()
        {
            { "Roles", value}
        }
    };
    await _graphServiceUsers[userId].Extensions.Request().AddAsync(extension);


    // Add extension with the guid from b2c_extensions_app  
    var extension2= new OpenTypeExtension()
    {
        ExtensionName = $"extension_00000000-e062-0000-8ec8-800000000003_Roles",
        AdditionalData = new Dictionary<string, object>()
        {
            { "Roles", value}
        }
    };

    await _graphServiceUsers[userId].Extensions.Request().AddAsync(extension2);

Neither of the scenarios above are linked to the B2C User that is returned with the claims.

I can retrieve the user with the extensions with the following code. And I get the extensions created from Graph API, but not the ones that are shown in the claims of the Token in the images above.

_graphServiceUsers[userId].Request().Select(UserSelectExpression).Expand("extensions").GetAsync();

All the custom attributes seem to be disconnected. I would need to update the ones that are returned as Claims, as they are the ones that I want to modify with the Graph API.

If you see the image below, you can see how the Extended properties are coming, but not the ones that I added from B2C. In the token you can see the mapped claim having one value, different from the value I added and retrieved from Graph API.

Different values from Claims and retrieved data using Graph API

I think they just made it too difficult, when it reality should be a lot easier to achieve something as simple as adding a new attribute and retrieve the values and work with them using Graph API.


Additional Info:

I'm using the Graph API application to access using the api

B2C Applications

I added all the permissions that had been suggested

Graph API permissions

In Graph Explorer (same results as C#):

https://graph.microsoft.com/v1.0/users/600001c5-0000-49b9-89c5-0000c80000fc?$select=displayName,identities,extension_39b4801c-5782-48d4-be6a-1cae6a8a881a_Roles

Result (Exception): Result Graph Explorer

Result C#

----------- UPDATE WITH SOLUTION --------------

I just wanted to add a quick note to clarify that the real problem, just in case someone has the same problem, as it's a bit complicated to get started with Graph API.

The problem (as of today) is that you need to use the Beta version of the Nuget packages or the Beta (graph.microsoft.com/beta) graph api to retrieve all the extension attributes. The stable version doesn't work.

  1. The nuget package that worked for me is Microsoft.Graph.Beta 4.21.0-preview.
  2. The package Microsoft.Graph stable 4.11.0 published Nov 25 2021 doesn't retrieve the extension properties.

Note: Something that is important is that the extension extension_39b0000c570000d4be6a1cae7a9a000a_Roles doesn't have the - from the guid.

Also, an additional thing to remember is the use of the guid from the extension to load only the extension: The first one to load all the attributes for the user. The second one to load only the Custom extended attribute.

    var user = await _graphServiceClient.Users[userId].Request().GetAsync();
    
    
    var user2 = await _graphServiceClient.Users[userId].Request().Select("id,extension_39b0000c570000d4be6a1cae7a9a000a_Roles").GetAsync();

Upvotes: 4

Views: 9539

Answers (4)

Ben Sampica
Ben Sampica

Reputation: 3402

Graph.Client v5 request to the https://graph.microsoft.com/v1.0/users endpoint looks like this in C#:

        var phoneNumberClaim = $"extension_{b2cClientId}_PhoneNumber";
        var graphResponse = await _graphServiceClient.Users[request.UserId].GetAsync(options =>
        {
            options.QueryParameters.Select = new string[] { "id", "displayName", "identities", phoneNumberClaim };
        }, cancellationToken: cancellationToken);
        var user = new GetUserResponse.UserItem
        {
            FullName = graphResponse!.DisplayName!,
            Email = graphResponse.Identities!.First(i => i.SignInType == "emailAddress").IssuerAssignedId!,
            PhoneNumber = graphResponse.AdditionalData[phoneNumberClaim]?.ToString(),
        };

        var response = new GetUserResponse
        {
            User = user
        };

        return response;

If you're testing it out using the Graph Explorer, make sure you're signed into the b2c tenant with a user. If you're in the wrong tenant, sign out and then append ?tenantId={tenantId} onto the url (i.e. https://developer.microsoft.com/en-us/graph/graph-explorer?tenant=myb2ctenant.onmicrosoft.com).

Example raw graph request on the graph explorer

https://graph.microsoft.com/v1.0/users/61000000-0000-0000-0000-00000000f297?$select=id,displayname,extension_480000000000000000000000000000a6_PhoneNumber,identities

Where the first id is the user object id and the extension id is the client id of the b2c application registration (not your application registration client id! - starts with b2c-extensions-app). For example, I had a Phone Number attribute.

Permissions I had on my application registration (not the b2c one) were as follows:

  • Directory.Read.All
  • Application.Read.All

Upvotes: 0

Ben Kleywegt
Ben Kleywegt

Reputation: 56

I could access custom properties using the standard .NET SDK for Microsoft.Graph API (not beta) in late 2022. I found the trick was to specify the property in the select clause.

var users = await graphServiceClient.Users.Request()
    .Select($"extension_{b2cExtensionsAppClientId}_Roles")
    .GetAsync();

The other trick was to get the Application (Client) Id of the b2c extensions app for including in the select clause. I could get this by navigating to App Registrations in the Azure Portal and clicking 'All Applications'. There I was shown the b2c-extensions-app where I could retrieve the app Id.

Upvotes: 1

Roberto
Roberto

Reputation: 63

I just wanted to add a quick note to clarify that the real problem, just in case someone has the same problem, as it's a bit complicated to get started with Graph API.

The problem (as of today) is that you need to use the Beta version of the Nuget packages or the Beta (https://graph.microsoft.com/beta) graph api to retrieve all the extension attributes. The stable version doesn't work.

  1. The nuget package that worked for me is Microsoft.Graph.Beta 4.21.0-preview.
  2. The package Microsoft.Graph stable 4.11.0 published Nov 25 2021 doesn't retrieve the extension properties.

Upvotes: 1

Tiny Wang
Tiny Wang

Reputation: 15961

Per my test, I can use this request to get extension properity:

https://graph.microsoft.com/v1.0/users/198axxxxx9ce1c?$select=id,country,extension_3d3xxxxx707e_tiny_custom_prop

test result is

enter image description here

And with asp.net core code, following this document:

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

// Values from app registration
var clientId = "application_clientid_that_registered_in_b2c";
var clientSecret = "app_client_secret";
var options = new TokenCredentialOptions
{
                AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var clientSecretCredential = new ClientSecretCredential(
                tenantId, clientId, clientSecret, options);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var user = graphClient.Users["198xxxxx9ce1c"].Request()
                    .Select("id,country,extension_3dxxxxx7e_tiny_custom_prop")
                    .GetAsync();
var res = user.Result;

enter image description here

===========================Update============================

You can see the claim is also a custom properity, and I got the value of this property of my specific user. The user I queried here is one which I signed up when redirect to the b2c sign up page, and the user flow is required to set this custom property.

enter image description here

I created an azure ad b2c application and set these api permission for this app.

enter image description here enter image description here

I used v1.0 version in my workaround, so I need to use $select feature to get extension property in my code, but if I used beta version here, I can get extension property directly.

enter image description here

enter image description here

Upvotes: 2

Related Questions