span
span

Reputation: 5624

How to set resource URL for GraphServiceClient to fetch Groups?

I am trying to fetch AD groups from the Graph API using the groups:src1 endpoint value that I receive from _claims_sources in the JWT access token.

My client is using Client Credentials and can fetch info for all users and groups in the AD.

This is how I set it up:

private async Task<IList<Group>> GetUserGroupsAsync(string endpoint)
{
    // Endpoint is from the claims in a previous OIDC request

    // Setup a client if it does not exist
    PrepareGraphApiClient();

    // I can fetch my groups using the methods in the client
    // var groupPage = await graphServiceClient.Me.MemberOf.Request().GetAsync();

    // This is where I would like to use the resource URL received from the
    // claims I receive in a previous OIDC request
    var groupPageFromUrl = ???

    ...
}

private void PrepareGraphApiClient()
{
    if (graphServiceClient != null) return;

    try
    {
        AuthenticationContext authority = new AuthenticationContext(oidcOptions.Authority);
        ClientCredential clientCredential = new ClientCredential(oidcOptions.ClientId, oidcOptions.ClientSecret);

        var graphApiResource = "https://graph.microsoft.com";
        AuthenticationResult authenticationResult = authority.AcquireTokenAsync(graphApiResource, clientCredential).Result;

        graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider(
            async requestMessage =>
            {
                // Add token to outgoing requests
                requestMessage.Headers.Authorization =
                    new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
            }));
    }
    catch (Exception ex)
    {
        logger.LogDebug($"Could not create the graph client {ex}");
        throw;
    }
}

Can I use the resource URL from the claims with the GraphServiceClient or do I have to set up an HttpClient to make the request?

Upvotes: 1

Views: 3321

Answers (1)

Philippe Signoret
Philippe Signoret

Reputation: 14336

The endpoint identified in _claim_sources.src1 is for Azure AD Graph, so the token you use needs to be for the Azure AD Graph API (https://graph.windows.net), not for Microsoft Graph (https://graph.microsoft.com). That also means you can't use the Microsoft Graph SDK, as the API requests and responses are fundamentally different.

You have two options:

  1. (Recommended) Use the fact that an endpoint is given only as an indication that you have to do the lookup, and make an equivalent call to Microsoft Graph using the Microsoft Graph SDK:

    var memberOfIds = await graphServiceClient
            .Users[userObjectId]                          # from the 'oid' in access token
            .GetMemberObjects(securityEnabledOnly: true) # true if groupMembershipClaims is "SecurityGroup", false if it's "All"
            .Request()
            .PostAsync();
    
  2. Use the endpoint given and build your own requests to Microsoft Graph using (for example) HttpClient. A quick'n'dirty example:

    using (var client = new HttpClient())
    {
        # Always get the token right before you use it. ADAL will take care of getting a new one
        # if needed, or using an existing cached token if not.
        authenticationResult = 
            authority.AcquireTokenAsync("https://graph.windows.net", clientCredential)
        client.DefaultRequestHeaders.Authorization = 
            new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
    
        # Make the API request to Azure AD Graph. Note we have to include the api-version and
        # the request body.
        HttpResponseMessage response = await client.PostAsync(
            $"{src}?api-version=1.6", new StringContent(
                "{'securityEnabledOnly': true}", # true if groupMembershipClaims is "SecurityGroup", false if it's "All"
                UnicodeEncoding.UTF8,
                "application/json"));
        if (response.IsSuccessStatusCode)
        {
            # Note we're deserializing as if it were a Microsoft Graph response to getMemberObjects,
            # rather than an Azure AD Graph response. Though it works, this is somewhat risky, and
            # it would be more correct to explicitly define the form of an Azure AD Graph response.
            var memberOfIds = JsonConvert.DeserializeObject<DirectoryObjectGetMemberObjectsCollectionResponse>(
                await response.Content.ReadAsStringAsync()).Value;
        }
    }
    

As a side note, I notice that you have the call to acquire a token outside of the DelegateAuthenticationProvider you've given to the Microsoft Graph library. You should put the AcquireTokenAsync call inside, so that it retrieves a fresh token for each and every Microsoft Graph request. The library (ADAL) will take care of using a cached token (if one is available), or making a new token request (if none are available or if those available are expired). Something like this:

    graphServiceClient = new GraphServiceClient(new DelegateAuthenticationProvider(
        async requestMessage =>
        {
            // Get fresh token
            AuthenticationResult authenticationResult = 
                await authority.AcquireTokenAsync(graphApiResource, clientCredential);

            // Add token to outgoing requests
            requestMessage.Headers.Authorization =
                new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
        }));

Upvotes: 3

Related Questions