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).
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.
After further research, I was able to find two solution for this, documented below.
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
}
}
}
// 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)