Connecting Azure Function to SharePoint list using Azure Function managed Identity sometime raise "There has been an error authenticating the request.";category="invalid_client""
I have an Azure Function which we enabled its managed Identity, as follow:-
then we run those commands, to allow the azure function to access SharePoint using the Azure Function managed identity, based on this technical documentation https://learn.microsoft.com/en-us/sharepoint/dev/apis/webhooks/sharepoint-webhooks-using-azd-template#grant-the-function-app-access-to-sharepoint-online:-
Script for Site.Selected
# This script requires the modules Microsoft.Graph.Authentication, Microsoft.Graph.Applications, Microsoft.Graph.Identity.SignIns, which can be installed with the cmdlet Install-Module below:
# Install-Module Microsoft.Graph.Authentication, Microsoft.Graph.Applications, Microsoft.Graph.Identity.SignIns -Scope CurrentUser -Repository PSGallery -Force
Connect-MgGraph -Scope "Application.Read.All", "AppRoleAssignment.ReadWrite.All"
$managedIdentityObjectId = "****" # 'Object (principal) ID' of the managed identity
$scopeName = "Sites.Selected"
$resourceAppPrincipalObj = Get-MgServicePrincipal -Filter "displayName eq 'Office 365 SharePoint Online'" # SPO
$targetAppPrincipalAppRole = $resourceAppPrincipalObj.AppRoles | ? Value -eq $scopeName
$appRoleAssignment = @{
"principalId" = $managedIdentityObjectId
"resourceId" = $resourceAppPrincipalObj.Id
"appRoleId" = $targetAppPrincipalAppRole.Id
}
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $managedIdentityObjectId -BodyParameter $appRoleAssignment | Format-List
& Script for Sites.FullControl.All
# This script requires the modules Microsoft.Graph.Authentication, Microsoft.Graph.Applications, Microsoft.Graph.Identity.SignIns, which can be installed with the cmdlet Install-Module below:
# Install-Module Microsoft.Graph.Authentication, Microsoft.Graph.Applications, Microsoft.Graph.Identity.SignIns -Scope CurrentUser -Repository PSGallery -Force
Connect-MgGraph -Scope "Application.Read.All", "AppRoleAssignment.ReadWrite.All"
$managedIdentityObjectId = "****" # 'Object (principal) ID' of the managed identity
$scopeName = "Sites.FullControl.All"
$resourceAppPrincipalObj = Get-MgServicePrincipal -Filter "displayName eq 'Office 365 SharePoint Online'" # SPO
$targetAppPrincipalAppRole = $resourceAppPrincipalObj.AppRoles | ? Value -eq $scopeName
$appRoleAssignment = @{
"principalId" = $managedIdentityObjectId
"resourceId" = $resourceAppPrincipalObj.Id
"appRoleId" = $targetAppPrincipalAppRole.Id
}
New-MgServicePrincipalAppRoleAssignment -ServicePrincipalId $managedIdentityObjectId -BodyParameter $appRoleAssignment | Format-List
And finally,
Connect-PnPOnline -Url "https://YOUR_SHAREPOINT_TENANT_PREFIX.sharepoint.com/sites/HR" -Interactive -ClientId "YOUR_PNP_APP_CLIENT_ID"
Grant-PnPAzureADAppSitePermission -AppId "***" -DisplayName "YOUR_FUNC_APP_NAME" -Permissions Manage
now here is the code to connect to the SharePoint list:-
string accessToken = await GetJwtTokenUsingSystemManagedIdentity();
string siteUrl = "https://**.sharepoint.com/sites/HR";
string listName = "Call Transfer Log Data";
string tenant = "***";
string site = "analytics";
string listTitle = "Call Transfer";
siteUrl = $"https://{tenant}.sharepoint.com/sites/{site}";
string apiBaseUrl = $"{siteUrl}/_api/web/lists/GetByTitle('{listTitle}')/items";
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
httpClient.DefaultRequestHeaders.Accept.ParseAdd("application/json;odata=verbose");
string filterDate = DateTime.UtcNow.AddDays(-120).ToString("yyyy-MM-ddTHH:mm:ssZ");
string requestUrl = $"{apiBaseUrl}?$filter=Modified ge datetime'{filterDate}'&$top=100&$orderby=Modified desc";
bool hasMore = true;
int page = 1;
List<CallTransferLogData> responseContent = new List<CallTransferLogData>();
while (hasMore)
{
Console.WriteLine($"Fetching page {page}...");
var request = new HttpRequestMessage(HttpMethod.Get, requestUrl);
var response = await httpClient.SendAsync(request);
Console.WriteLine($"row response = " + response);
string content = await response.Content.ReadAsStringAsync();
using JsonDocument doc = JsonDocument.Parse(content);
var root = doc.RootElement.GetProperty("d");
Console.WriteLine($"Building Root {page}...");
// Process results
foreach (var item in root.GetProperty("results").EnumerateArray())
{ //code goes here
}
}
private static async Task<string> GetJwtTokenUsingSystemManagedIdentity()
{
string resource = "https://***.sharepoint.com/.default";
var credential = new DefaultAzureCredential();
var tokenRequestContext = new TokenRequestContext(new[] { resource });
var token = await credential.GetTokenAsync(tokenRequestContext);
//Console.WriteLine("Toekn is " + token.Token)
; return token.Token;
}
Now the azure function is suppose to run each day at 4 & 6 am UTC(). where yesterday at 11 pm UTC() , i deployed the azure function and to test it, i set its execution time to be 11:15 PM UTC(), and worked well, it got the items from SharePoint. then i modify the azure function to run at 4 & 6 am utc(). but today at 8 am UTC() i checked the logs and the call to SharePoint failed wit this exception:-
row response = StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
x-ms-diagnostics: 3001000;reason="There has been an error authenticating the request.";category="invalid_client"
so i did a new deployed to test the azure function and i forced it to run at 8:30 am UTC(), but it raised the same error, so i did a new deployed and i set the execution time to be at 9 am UTC(), and i stopped the azure function after the deployment and i start it again.. now the call to SharePoint succeed, as follow:-
row response = StatusCode: 200, ReasonPhrase: 'OK', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:
so what is going on? is the reason that i need to stop/start the function after each deployment? or the azure function after certain amount of time, will fail to connect using its managed identity and will need a new deployment, and a new restart ?? Please any help on this?
Thanks