четверг, 7 мая 2020 г.

Using SharePoint REST APIs from Azure Functions with .net core


There is often a need to build C# APIs that update something or look something up from SharePoint online. Currently, if using Azure Functions and .net core, we are left with only one officially supported option: Using the Graph APIs.

To use Graph APIs we must register an app in Azure AD App registrations and grant it app permission to SharePoint-related scopes. The problem with this approach is that we cannot limit the app permissions to a certain site or list – it can only cover the entire tenant. This approach works for demo tenants, but often not possible in enterprise environments.

With SharePoint app registrations created with appregnew.aspx it is possible to limit the app permissions by using the app permissions XML, but these apps cannot be used with graph APIs. Using these apps required CSOM or SharePoint PnP packages, which are not available for .net core. 

It seems that we are forced to either create classic web apps with Web APIs or use Azure Functions v1 that still support .net framework. These are valid options, but they sound outdated in 2020.

 I was looking for something better, and I found one good option to use with .net core. You can use the SharePoint REST APIs directly without any third party libraries, but you need to obtain an access token via your own code. 

To obtain the access token, we would need a SharePoint app registration created with /_layouts/15/appregnew.aspx.

First, we need to get the OAuth2 endpoint for your tenant. To do it, I created the following function that goes to tenants endpoint reference page, and pickes the Oauth2 endpoint URL:

private static async Task<string> GetAuthUrl(string realm)
{
    var url = "https://accounts.accesscontrol.windows.net/metadata/json/1?realm=" + realm;
    HttpClient hc = new HttpClient();
    var r = await (await hc.GetAsync(url)).Content.ReadAsAsync<AccessControlMetadata>();
    return r.endpoints.FirstOrDefault(e => e.protocol == "OAuth2")?.location;
}

The next step is to get the access token. Here is the a function for that:

public static async Task<string> GetAccessToken(Uri siteUrl, string clientId, string tenantId, string clientSecret)
{
    var authUrl = await GetAuthUrl(tenantId);
    // This is the SharePoint Service Principal ID, it is the same in all tenants
    string serviceId = "00000003-0000-0ff1-ce00-000000000000";
    var _resource = $"{serviceId}/{siteUrl.Authority}@{tenantId}";
    var fullClientId = $"{clientId}@{tenantId}";
    HttpClient hc = new HttpClient();
    FormUrlEncodedContent content = new FormUrlEncodedContent(new KeyValuePair<string, string>[] {
        new KeyValuePair<string, string>("grant_type", "client_credentials"),
        new KeyValuePair<string, string>("client_id", fullClientId),
        new KeyValuePair<string, string>("client_secret", clientSecret),
        new KeyValuePair<string, string>("resource", _resource)
    });
    HttpResponseMessage httpResponseMessage = await hc.PostAsync(authUrl, content);
    var result = await httpResponseMessage.Content.ReadAsAsync<AcsResults>();
    return result.access_token;
}

With these 2 functions, you can then use the SharePoint REST APIs without any NuGet packages or dependencies:

var url = "https://inyushin.sharepoint.com/sites/test";
var accessToken = await GetAccessToken(new Uri(url), clientId, tenantId, clientSecret);
using (var hc = new HttpClient())
{
    hc.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    hc.DefaultRequestHeaders.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json;odata=verbose"));
   
    var r = await hc.GetAsync("https://inyushin.sharepoint.com/sites/test/_api/web?$select=Title");
    var str = await r.Content.ReadAsStringAsync();
}

I hope you find this useful. Feel free to comment or propose improvements.

Комментариев нет:

Отправить комментарий