Azure Function using a Managed Identity to call SharePoint Online

This post will detail how to use a Managed Identity from an Azure Function to make calls to the SharePoint Online REST API.

Azure Managed Identities offer a way to allow Azure Services to “self authenticate” against other Azure Services. I tend to think of them as “service accounts in the cloud”.

1.The first step is to create an Azure Function, let’s call ours testmitosharepoint.

2. Next we click Platform features->Identity, use the default of System assigned and then choose On and click the Save button:

3. You’ll see a prompt asking explaining you are creating an object in Active Directory, click the Yes button:

4. You’ll then see the objectid of the service principal that has just been created, copy this id as we’ll need it later when we add the permissions.

5. At this point we have configured our Function App to have an identity in Azure AD and we can use this identity to retrieve a bearer token and pass the bearer token to calls to the SharePoint Online REST API.

6. The Managed Identity is basically an Azure AD Service Principal. We can see the details of this Service Principal (and modify it) in various ways. Using the Microsoft Graph Explorer is one such way. Browse to https://aka.ms/ge and sign in (if this is the first time you’ve used the GE then you’ll have to consent to the app).

7. Once signed in we want to use the servicePrincipals endpoint (at the time of writing part of the beta API):

https://docs.microsoft.com/en-us/graph/api/serviceprincipal-list?view=graph-rest-beta

8. Note that to call this API you’ll might need to grant yourself further permissions:


9. We can retrieve the details of the Managed Identity Service Principal using the following query (substitute the object id you copied above at step 4):

https://graph.microsoft.com/beta/serviceprincipals/29f7ec8c-07eb-4c94-983b-acd806e910c7

You should see the details of your service principal in the results:

{
"@odata.context": "https://graph.microsoft.com/beta/$metadata#servicePrincipals/$entity",
"id": "29f7ec8c-07eb-4c94-983b-acd806e910c7",
"deletedDateTime": null,
"accountEnabled": true,
"appDisplayName": null,
"appId": "ee26a3db-e89c-440e-94a7-1ba20b58380f",
"appOwnerOrganizationId": null,
"appRoleAssignmentRequired": false,
"displayName": "testmitosharepoint",
"errorUrl": null,
"homepage": null,
"info": null,
"logoutUrl": null,
"publishedPermissionScopes": [],
"preferredTokenSigningKeyThumbprint": null,
"publisherName": null,
"replyUrls": [],
"samlMetadataUrl": null,
"servicePrincipalNames": [
"ee26a3db-e89c-440e-94a7-1ba20b58380f",
"https://identity.azure.net/nFoc2cyj+BaqZHpNGXdazrwSbFP50bLwojc994uTgLI="
],
"signInAudience": null,
"tags": [],
"addIns": [],
"appRoles": [],
"keyCredentials": [
{
"customKeyIdentifier": "1EEBD51734B62D816F3839CD214A525B0F39DE35",
"endDateTime": "2019-06-15T19:03:00Z",
"keyId": "2b3761fc-1e0b-4816-b955-e45245bc9aef",
"startDateTime": "2019-03-17T19:03:00Z",
"type": "AsymmetricX509Cert",
"usage": "Verify",
"key": null,
"displayName": "CN=/subscriptions/06c69857-8582-4e2f-9a83-6d2840327e73/resourcegroups/testmitosharepointrg"
}
],
"passwordCredentials": []
}

Of particular note above is that this service principal has an X509 Certificate credential, this Certificate is managed by Azure and we do not have to worry about it expiring or being trusted etc., Azure takes care of rolling this over for us. This is important as to make App Only calls to the SharePoint Online REST API we need to present certificate credentials.

10. Let’s check this service principal using the AzureAD Powershell commandlets. First login to AzureAD from Powershell and then run the following:

Get-AzureADServicePrincipal -SearchString “testmitosharepoint”


11. So we can see the service principal from the Graph Explorer and we can also see the Service Principal from AzureAD Powershell. But what we want to do is not just see the service principal, we also want to add the appropriate App Only permissions so it can be used to call the SharePoint Online REST API. First we need to see the details of what App Only permissions are available.

12. We want to get the list of App Only roles that are available for SharePoint Online. SharePoint Online in fact has its own service principal. We can get the basic details of this by running the following powershell (note we want to make a note of the ObjectId as we’ll be using that later on as the ResourceId parameter we pass to the New-AzureADServiceAppRoleAssignment commandlet):

Get-AzureADServicePrincipal -SearchString “Office 365 SharePoint”

13. Next we want to see all of the App Only roles that this service principal offers. We can do that from Powershell by calling the following:

Get-AzureADServicePrincipal -SearchString “Office 365 SharePoint” | %{$_.AppRoles} | fl

This will present us with the following output:

AllowedMemberTypes : {Application}
Description : Allows the app to read user profiles without a signed in user.
DisplayName : Read user profiles
Id : df021288-bdef-4463-88db-98f22de89214
IsEnabled : True
Value : User.Read.All
AllowedMemberTypes : {Application}
Description : Allows the app to read and update user profiles and to read basic site info without a signed in user.
DisplayName : Read and write user profiles
Id : 741f803b-c850-494e-b5df-cde7c675a1ca
IsEnabled : True
Value : User.ReadWrite.All
AllowedMemberTypes : {Application}
Description : Allows the app to write enterprise managed metadata and to read basic site info without a signed in user.
DisplayName : Read and write managed metadata
Id : c8e3537c-ec53-43b9-bed3-b2bd3617ae97
IsEnabled : True
Value : TermStore.ReadWrite.All
AllowedMemberTypes : {Application}
Description : Allows the app to read enterprise managed metadata and to read basic site info without a signed in user.
DisplayName : Read managed metadata
Id : 2a8d57a5-4090-4a41-bf1c-3c621d2ccad3
IsEnabled : True
Value : TermStore.Read.All
AllowedMemberTypes : {Application}
Description : Allows the app to read, create, update, and delete document libraries and lists in all site collections without a signed in user.
DisplayName : Read and write items and lists in all site collections
Id : 9bff6588-13f2-4c48-bbf2-ddab62256b36
IsEnabled : True
Value : Sites.Manage.All
AllowedMemberTypes : {Application}
Description : Allows the app to have full control of all site collections without a signed in user.
DisplayName : Have full control of all site collections
Id : 678536fe-1083-478a-9c59-b99265e6b0d3
IsEnabled : True
Value : Sites.FullControl.All
AllowedMemberTypes : {Application}
Description : Allows the app to read documents and list items in all site collections without a signed in user.
DisplayName : Read items in all site collections
Id : d13f72ca-a275-4b96-b789-48ebcc4da984
IsEnabled : True
Value : Sites.Read.All
AllowedMemberTypes : {Application}
Description : Allows the app to create, read, update, and delete documents and list items in all site collections without a signed in user.
DisplayName : Read and write items in all site collections
Id : fbcd29d2-fcca-4405-aded-518d457caae4
IsEnabled : True
Value : Sites.ReadWrite.All

14. What we now need is to select what Application Role we want to assign to our Managed Identity service principal and make a note of it. I want to add to my “testmitosharepoint” the App Only role of Sites.Read.All, so from the above I need to make a note of the Id d13f72ca-a275-4b96-b789-48ebcc4da984 as this will be passed to the New-AzureADServiceAppRoleAssignment commandlet as the Id parameter.

15. So we now have the details of our service principal “testmitosharepoint”, we have the details of the SharePoint Online service princpal, and we have the details of the App Only role we want to grant to our service principal. Next we want to call the New-AzureADServiceAppRoleAssignment commandlet (for both the ObjectId and the PrincipalId we pass our Managed Identity service principal ObjectId):

New-AzureADServiceAppRoleAssignment -ObjectId 29f7ec8c-07eb-4c94-983b-acd806e910c7 -PrincipalId 29f7ec8c-07eb-4c94-983b-acd806e910c7 -ResourceId 60ce75ac-957c-4f93-b382-d7f85cbbe649 -Id d13f72ca-a275-4b96-b789-48ebcc4da984

Note: You will see an error at this point, but it appears that the operation succeeds. See the following StackOverflow post for more details:

https://stackoverflow.com/questions/52557766/assigning-microsoft-graph-permissions-to-azure-managed-service-identity?noredirect=1&lq=1

Update 19th March 2019: Arturo Lucatero (Senior Program Manager at Microsoft) tweeted the following:

16. To check if the permissions have been added we can run the following Powershell:

Get-AzureADServiceAppRoleAssignedTo -ObjectId 29f7ec8c-07eb-4c94-983b-acd806e910c7

17. We now have granted the correct permissions on the service principal, we can now return to the Azure Portal and add an Azure Function that will use the Managed Identity’s token to call the SharePoint Online REST API.

18. Back in the Azure Portal browse to the function, and then browse to the testmitosharepoint function app, and add a new function using the defaults of HTTP Trigger, authentication Function (note the authentication doesn’t matter for now as we are just testing, of course you’d want to configure this to what you require).

19. Add the following two lines of code immediately under the first log.LogInformation line, save and run the function and inspect the output:

log.LogInformation("MSI_ENDPOINT: " + System.Environment.GetEnvironmentVariable("MSI_ENDPOINT"));    log.LogInformation("MSI_SECRET: " + System.Environment.GetEnvironmentVariable("MSI_SECRET"));

20. You should see something like the following output:

21. What you are seeing is the “local” endpoint details that the underlying infrastructure puts in place when you configure your Function to use a Managed Identity. Note that basically what we get is an http endpoint listening on 127.0.01 and some port, this is local to the App Service the Function is hosted on. If you wanted to get your Managed Identity of a VM you could add a simple command line app and make the exact same call as the VM would “host” this local endpoint and have the environment variables configured in the same way.

22. Next we will add the following code to the function immediately under the two log.LogInformation lines we just added (make sure you enter your tenant name :

HttpClient client = new HttpClient();    
string sharePointResourceId = "https://<yourtenant>.sharepoint.com";    
string apiVersion = "2017-09-01";    
client.DefaultRequestHeaders.Add("Secret", System.Environment.GetEnvironmentVariable("MSI_SECRET"));    
var tokenResponse = await client.GetAsync(String.Format("{0}/?resource={1}&api-version={2}", System.Environment.GetEnvironmentVariable("MSI_ENDPOINT"), sharePointResourceId, apiVersion));    
var rawContent = await tokenResponse.Content.ReadAsStringAsync();    log.LogInformation("MI Response: " + rawContent);    
dynamic managedIdentityApiResponseData = JsonConvert.DeserializeObject(rawContent);    
string managedIdentityBearerToken = null;    
managedIdentityBearerToken =  managedIdentityBearerToken ??managedIdentityApiResponseData?.access_token;    
log.LogInformation("Managed Identity Access Token: " + managedIdentityBearerToken);

23. What we “should” see is an output similar to the below:

2019-03-18T17:35:35.458 [Information] Script for function 'HttpTrigger1' changed. Reloading. 2019-03-18T17:35:35.541 [Information] Compilation succeeded. 2019-03-18T17:35:36.264 [Information] Executing 'Functions.HttpTrigger1' (Reason='This function was programmatically called via the host APIs.', Id=416bacbe-9ec9-4246-8ed4-f53803cb7d4a) 2019-03-18T17:35:36.404 [Information] C# HTTP trigger function processed a request. 2019-03-18T17:35:36.404 [Information] MSI_ENDPOINT: http://127.0.0.1:41031/MSI/token/ 2019-03-18T17:35:36.404 [Information] MSI_SECRET: DA23480116914E918792DD4A6CDC16B0 2019-03-18T17:35:36.421 [Information] MI Response: {"access_token":"eyJ<truncated for brevity>ubVHVxsw","expires_on":"3/19/2019 1:25:16 AM +00:00","resource":"https://finarne.sharepoint.com","token_type":"Bearer"} 2019-03-18T17:35:36.429 [Information] Managed Identity Access Token:  
eyJ<truncated for brevity>ubVHVxsw 2019-03-18T17:35:36.432 [Information] Executed 'Functions.HttpTrigger1' (Succeeded, Id=416bacbe-9ec9-4246-8ed4-f53803cb7d4a)

24. We can see the raw output from the Managed Identity “local” api – a Json object that contains various properties of our service principal token (formatted for clarity):

{    
   "access_token": "eyJ<truncated for brevity>ubVHVxsw",    
   "expires_on": "3/19/2019 1:25:16 AM +00:00",    
   "resource": "https://<yourtenant>.sharepoint.com",    
   "token_type": "Bearer"
}

25. We have also stored the access_token value in the variable managedIdentityBearerToken, we will use this value when we call the SharePoint Online REST API. But before we do that one thing we can do to “check” this token is to browse to https://jwt.ms and paste in the access_token value:

26. We can also click the Claims tab to see additional details (just showing the basic details here, lots more if you scroll down the page):

27. Now we can go back to our Azure Function and test calling into the SharePoint Online REST API. Add the following code immediately under the log.LogInformation(“Managed Identity Access Token: ” + managedIdentityBearerToken); line (we are simply calling back to the root site on the tenant and getting the details of the _api/web call):

string sharePointOnlineRESTAPIEndPoint = "https://<yourtenant>.sharepoint.com/_api/web";
HttpClient sharePointRESTAPIClient = new HttpClient();
sharePointRESTAPIClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + managedIdentityBearerToken);
sharePointRESTAPIClient.DefaultRequestHeaders.Add("Accept", "application/json");
var sharePointRESTAPIResponse = await sharePointRESTAPIClient.GetAsync(sharePointOnlineRESTAPIEndPoint);
var sharePointRESTAPIRawContent = await sharePointRESTAPIResponse.Content.ReadAsStringAsync();
log.LogInformation("SharePoint REST API Response: " + sharePointRESTAPIRawContent);

28. Save and run the code, you should see output from the SharePoint REST API.

29. If you want to add other API permissions to the Managed Identity (for example call back to the Microsoft Graph endpoints) you can use the Powershell in steps 12 & 13 to discover what App Only Roles are available, all you need to do is to change the -SearchString parameter from “Office 365 SharePoint Online” to “Microsoft Graph”:

30. You will see a LOT of results, but the same principals apply. You choose the App Only permissions you want, add then using the details in steps 14 & 15 grant the permissions, when you request the Managed Identity access token at step 22 you would pass in “https://graph.microsoft.com&#8221; as the resource parameter:

HttpClient client = new HttpClient();
string microsoftGraphResourceId = "https://graph.microsoft.com";
string apiVersion = "2017-09-01";
client.DefaultRequestHeaders.Add("Secret", System.Environment.GetEnvironmentVariable("MSI_SECRET"));
var tokenResponse = await client.GetAsync(String.Format("{0}/?resource={1}&api-version={2}", System.Environment.GetEnvironmentVariable("MSI_ENDPOINT"), microsoftGraphResourceId, apiVersion));

31. You can repeat the same steps to examine the token in the https://jwt.ms website and you can then make calls to the Microsoft Graph the same way you called the SharePoint Online REST API, all you need to do is change the HttpClient calls with the correct endpoint.

Summary
This post has looked at how you can add App Only permissions to the Managed Identity service principal using the most basic steps. There are SDKs that will help in calling the “local” endpoint and also calling the SharePoint Online REST API, all I wanted to do here was look at doing all of this at the most basic level.

YMMV

5 thoughts on “Azure Function using a Managed Identity to call SharePoint Online

  1. Pingback: Provisioning Teams with Azure Functions and Microsoft Flow Part 5: A Change in Direction | Bob German's Vantage Point

  2. Suny Abraham

    Hi
    In my case I donot get any Approles listed in my environment for “Office 365 Sharepoint”
    Any suggestions
    Thank You !

    Reply
    1. finarne Post author

      Hi Suny, can you give me some more details? Are you saying at step 13 when you run Get-AzureADServicePrincipal -SearchString “Office 365 SharePoint” | %{$_.AppRoles} | fl you don’t get any results?

      Reply
  3. Nicola

    That looks great.
    I am struggling in getting rid of service accounts.
    I have a very simple use case.
    I have a SharePoint list, an di want to notify users via email whenever an item is added/modified.
    So far I only found solution working with Flows, and therefore with a licensed user account.
    I just deployed a LogicApp and thanks to SendGrid integration I can now send the notification email from a mailbox that is not tied to a licensed user account.
    However for SharePoint, the only option I have in LogicApp is to connect with a username and password.
    Is there a way to access SharePoint with a Managed Identity in a LogicApp?
    Thanks in advance.
    Nicola

    Reply
    1. finarne Post author

      Hi Nicola, Logic Apps support Managed Identities, check here: https://docs.microsoft.com/en-us/azure/logic-apps/create-managed-service-identity and here for the actions: https://docs.microsoft.com/en-us/azure/logic-apps/logic-apps-securing-a-logic-app#managed-identity-authentication

      You would follow the steps in the first link to create the Managed Identity, then use the steps in this blog post to grant the permissions, then once the MI is created and permissions added you can use the HTTP Action to send requests to the SharePoint REST API (or even the Graph sites API, just remember to grant the permissions and select the correct resource parameter in the HTTP Action).

      Also, if you want an alternative to using a Managed Identity in Logic Apps check out Laura’s post on authenticating to SharePoint using a App Only permissions with a certificate: https://laurakokkarinen.com/authenticating-to-office-365-apis-with-a-certificate-step-by-step/

      Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.