Search code examples
c#sharepoint-onlinetaxonomynon-interactivepnp-framework

How to query SharePoint Term Store using App only credential in C#


I am trying to build an application that gets terms from a term store in SharePoint Online, using App only permissions. For that, I was hoping to leverage the PnP Framework SDK.

I know for a fact that, currently, the Graph API does not support term store commands using app only permissions (for either v1 or beta).

enter image description here

I can see here how to obtain a ClientContext using app only permissions (client_id and client_secret). But this does not give me access to the term store.

Here, a PnPContext is used to interact with the term store, but the configuration options don't seem to have an option to provide the tenant_id, client_id, and client_secret.

Is there any way to do this?

PS: I only need read access to the term store.


Solution

  • After further research, I was able to find two solution for this, documented below.

    1. Using PnP Framework
    var vars = Environment.GetEnvironmentVariables();
    var tenant_id = vars["SHAREPOINT_TENANT_ID"]?.ToString() ?? "";
    var sp_client_id = vars["SHAREPOINT_CLIENT_ID"]?.ToString() ?? "";
    var sp_client_secret = vars["SHAREPOINT_CLIENT_SECRET"]?.ToString() ?? "";
    
    using (ClientContext cc = new AuthenticationManager().GetACSAppOnlyContext(_siteUrl, sp_client_id, sp_client_secret))
    {
        var taxonomySession = TaxonomySession.GetTaxonomySession(cc);
        TermStore termStore = taxonomySession.GetDefaultSiteCollectionTermStore();
        var termGroup = termStore.GetGroup(new Guid(_amlinkTermSetGroupId));
    
        cc.Load(termGroup,
            group => group.Name,
            group => group.TermSets
                .Include(
                    termSet => termSet.Name,
                    termSet => termSet.Terms.Include(
                        term => term.Name,
                        term => term.Labels.Include(
                            label => label.Value
                            )
                    )
            )
        );
    
        cc.ExecuteQuery();
    
        if ((taxonomySession == null) || (termStore == null) || (termGroup == null))
        {
                return;
        }
    
        foreach (TermSet termSet in termGroup.TermSets)
        {
            foreach (Term term in termSet.Terms)
            {
                    // do something with termGroup, termSet, term, and term.Lables
            }
        }
    }
    
    1. Using SharePoint REST API
    // You should be caching the token if you got with this option
    
    string resource = $"00000003-0000-0ff1-ce00-000000000000/{domain}@{tenant_id}";
    
    var client_id = $"{sp_client_id}@{tenant_id}";
    OAuth2AccessTokenRequest oauth2Request = OAuth2MessageFactory.CreateAccessTokenRequestWithClientCredentials(client_id, sp_client_secret, resource);
    oauth2Request.Resource = resource;
    
    // Get token
    OAuth2S2SClient authClient = new OAuth2S2SClient();
    
    OAuth2AccessTokenResponse oauth2Response;
    try
    {
        string authUrl = $"https://accounts.accesscontrol.windows.net/{tenant_id}/tokens/OAuth/2";
        oauth2Response = (OAuth2AccessTokenResponse)authClient.Issue(authUrl, oauth2Request);
    }
    catch (WebException wex)
    {
        Stream? stream = wex.Response?.GetResponseStream();
        if (stream != null)
        {
            using StreamReader sr = new(stream);
            string responseText = sr.ReadToEnd();
            throw new WebException(wex.Message + " - " + responseText, wex);
        }
    
        throw;
    }
    
    string accessToken = oauth2Response.AccessToken;
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    
    var url = $"https://{{domain}}/_api/v2.1/termStore/sets/{_termSetId}/terms";
    var response = await client.GetAsync(url);
    
    if (response.IsSuccessStatusCode)
    {
        var termsResponse = await response.Content.ReadFromJsonAsync<TermsResponse>();
        // do something with the reponse
    }
    

    Some useful links:

    SharePoint Online Retrieve term store data including Labels using .Net managed object model

    Using .NET Standard CSOM and MSAL.NET for App-Only auth in SharePoint Online

    CSOM for .NET Standard with SharePoint App-only principal

    Using CSOM for .NET Standard instead of CSOM for .NET Framework (implementation of token cache)