Presenting at Collab365 GlobalCon3

I have the privilege of presenting “Introduction to Microsoft Graph Development” at the upcoming Collab365 GlobalCon3 taking place Sept 8-11, 2020.  This is a free online conference with MVPs and experts from around the world presenting on developer, IT Pro, and adoption topics.

Title: Introduction to Microsoft Graph Development

Abstract: “I hear that I need to use Microsoft Graph for developing against Office 365 but I have no clue where to start.” “I want to grant access to company data without throwing in the entire kitchen sink.” Fear not fellow developers and admins. This session we will ramp you up to a 200 level knowledge on the pertinent parts of Microsoft Graph including endpoints available, syntax, authentication flows, and more. We will also cover useful examples of what can be accomplished using these APIs. Prior experience with Microsoft Graph is not required but can be helpful.

You can also purchase an all-access pass which includes lifetime access to the videos, additional e-books, and more.  Looking forward to participating in this great event.

Calling Microsoft Graph Endpoint with Delegated Implicit Authentication Does Not Include Azure AD Roles

Recently I was working with a Microsoft Graph partner and ran into an interesting scenario around calling Microsoft Graph endpoints from SharePoint Framework (SPFx) web parts using delegated permissions that I want to share.

Scenario

The partner was building a SPFx web part that was making calls to Microsoft Graph using the MSGraphClient. While making calls to specific endpoints on Microsoft Graph they were receiving a 403 Forbidden error response. We checked the permissions granted and consented and everything appeared in order.

403 Forbidden error screenshot.

Digging deeper into the MSGraphClient implementation I found that it uses an ImplicitMSALAuthenticationProvider for acquiring the authentication token. Implicit authentication is important to keep in mind in this scenario.

Use the MSGraphClient to connect to Microsoft Graph https://docs.microsoft.com/en-us/sharepoint/dev/spfx/use-msgraph

Microsoft Graph JavaScript Client Library – Authenticate for the Microsoft Graph service https://www.npmjs.com/package/@microsoft/microsoft-graph-client#2-authenticate-for-the-microsoft-graph-service

I used https://jwt.ms (provided by the Microsoft Identity Platform team) to decode a sample token from the partner and then again to decode an access token I had acquired in my lab environment. I noticed that the partner’s access token did not have the “wids” claim while my lab access token did have that claim.

Thanks to a contact in O365 software engineering who was able to confirm that the “wids” claim contains the tenant-wide roles assigned to the user. As noted in the documentation implicit authentication flows may not return the “wids” claim due to token length concerns.

Screenshot of documentation on "wids" claim.  Highlight that this claim might not be returned for implicit authentication flow.

Microsoft identity platform access tokens – payload claims

https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens#payload-claims

Looking at one of the Microsoft Graph endpoints that the partner was calling (getOffice365GroupsActivityDetail) we found the below note explaining that when using delegated permissions (which the partner was using) the user context must also be assigned to an appropriate Azure AD limited administrator role.

Note: For delegated permissions to allow apps to read service usage reports on behalf of a user, the tenant administrator must have assigned the user the appropriate Azure AD limited administrator role. For more details, see Authorization for APIs to read Microsoft 365 usage reports.

https://docs.microsoft.com/en-us/graph/api/reportroot-getoffice365groupsactivitydetail?view=graph-rest-1.0#permissions

Putting the pieces together, the query was failing an authentication check because the access token passed to the endpoint did not have the necessary claim containing the assigned Azure AD roles. Hence the “invalid permissions” response.

Conclusion

This is an edge case scenario that took some collaboration with various groups within Microsoft to track down. Many thanks to my peers who helped with identifying additional information as we investigated. I submitted a pull request to the SPFx documentation that has been merged to call out this behavior (see Known Issues on this link). So far that I can tell only the Microsoft 365 usage reports endpoints on Microsoft Graph may have an Azure AD role requirement.

Authorization for APIs to read Microsoft 365 usage reports
https://docs.microsoft.com/en-us/graph/reportroot-authorization

Hopefully this post helps others who may run into this scenario. If you find additional similar scenarios feel free to let me know in the comments.

-Frog Out

A New Role with Microsoft Graph Team

For the past ~9 years I have had the personal and professional pleasure to be a Premier Field Engineer (PFE) with Microsoft. I love the passion and knowledge that my peers and I share on a daily basis with our customers and each other. Recently though an opportunity opened up that I couldn’t say no to.

Starting May 26th I am joining the Microsoft Graph team as a Sr. Customer & Partner Experience (CPX) PM. This is an entirely new role for the team and I will be the first member. I’m looking forward to the new opportunities and working with amazing teammates, many of whom I’ve worked with on side projects for the past 1-2 years.

I plan to continue writing content for my personal blog at least every other month, but you may see more Microsoft Graph related content or cross postings on the Microsoft Graph Blog. Considering that my highest viewed posts in the past few years have been Microsoft Graph related that may not be much of a change though 😉. I’ll also be more active on the newly released Microsoft Q&A site as well as Stack Overflow under the “microsoft-graph” tag.

Thanks to everyone who has helped and encouraged me in my growth with Microsoft Graph. Special thanks to Jeremy Thake, Yina Arenas, Jason Johnston, Darrel Miller, Vincent Biret, Gavin Barron, Srinivas Varukala, and many more.

-Frog Out

Creating a C# Azure Function to Call Microsoft Graph

This post is a part of The Third Annual C# Advent.

Building on my “Introduction to Calling Microsoft Graph from a C# .Net Core Application” post from the 2018 C# Advent event, this year we’ll take what we learned and adapt that code to run in an Azure Function.  I recommend reading that post (and the linked resources in it) first to get the background on creating and authenticating an Azure AD application.

Prerequisites

Background

Azure Functions is one of the serverless options within Azure (read here for more about Azure serverless solutions).  Azure Functions consist of a trigger (HTTP, timer, storage event, etc.) and optionally one or more input / output bindings.  I especially appreciate how triggers and input / output bindings reduce, or even completely remove, the need to write what I call “internal plumbing” code.  “Internal plumbing” code is the code required to connect to an Azure storage account, monitor for a webhook request, or similar event based functionality.  Having that functionality taken care of allows you as the developer to focus on writing the core business logic where the most value can be provided.

Speaking of bindings, you may notice on the Azure Functions documentation that there are Microsoft Graph bindings for Azure Functions as of version 2+.  I’m not on the product group but as far as I’m aware these bindings have been in preview for over 2 years and are not planned to move past preview.  As such this blog post will walk through calling Microsoft Graph using the Microsoft Graph .Net SDK.

The below steps can be seen in the sample repo at BTJ.CSAdvent.AZFunc on GitHub. Additionally the Develop Azure Functions by using Visual Studio Code documentation gives a good overview of the VS Code development process.

Create Azure Function

Open Visual Studio Code and open the Command Pallete (⇧⌘P or F1 on Mac, Ctrl+Shift+P or F1 on Windows).

  1. Search for “Azure Functions: Create New Project…” and select it.CSAdvent-MSGAZFunc1
  2. Choose a folder to create the project in (preferably not in a Visual Studio Code workspace).
  3. Select the language of the project as C#.
    CSAdvent-MSGAZFunc2
  4. Create an empty function project by selecting “Skip for now” when prompted for the trigger type.
    CSAdvent-MSGAZFunc3
  5. Open the Command Palette again and search for “Azure Functions: Create Function…” and select it.
    CSAdvent-MSGAZFunc4
  6. Select a TimerTrigger.
  7. Name the function “GetUserMicrosoftGraph” (or any name you wish, but later references will be impacted).
    CSAdvent-MSGAZFunc5
  8. Provide a namespace for the function.
  9. Use the default timer schedule “0 */5 * * * *” which is the CRON representation of running every 5 minutes.
  10. On the terminal at the root of the project run “dotnet build” to build the project.
    • If you are prompted to choose a storage account and are using Windows, select “Use local emulator”.
    • If you are on Mac / Linux, select “Select storage account” and specify a cloud Azure storage account.CSAdvent-MSGAZFunc6

At this point you should have an Azure Function project that should build successfully.

Add NuGet Packages

Since we’ll be calling Microsoft Graph and the MSAL .Net SDK libraries we’ll add the supporting NuGet packages with the following:

  1. Run “dotnet add package Microsoft.Graph” from the terminal.
  2. Run “dotnet add package Microsoft.Identity.Client” from the terminal.

Add Azure AD App Authentication

Open the local.settings.json file.  It should look similar to the following (AzureWebJobStorage may be different depending on choices above for storage).

CSAdvent-MSGAZFunc7

Add the following key/value pairs to the Values section.  Use the values from the Azure AD application that was created using the references in the linked articles above.

   "timerSchedule": "0 */5 * * * *",
   "AzureADAppTenantId": "YOUR_TENANT_ID_HERE",
   "AzureADAppClientId": "YOUR_CLIENT_ID_HERE",
   "AzureADAppClientSecret": "YOUR_CLIENT_SECRET_HERE",
   "AzureADAppRedirectUri": "YOUR_REDIRECT_URI_HERE"

Implement the sample code (link here) from last year’s post as helper classes to the project.  See AuthHandler.cs and MsalAuthenticationProvider.cs from the example repo.

CSAdvent-MSGAZFunc11

Call Microsoft Graph

Implement the following code (adapted from last year’s post, link here) inside the GetUserMicrosoftGraph.cs file.

 public static class GetUserMicrosoftGraph
    {
        private static GraphServiceClient _graphServiceClient;

        [FunctionName("GetUserMicrosoftGraph")]
        public static void Run([TimerTrigger("%timerSchedule%")]TimerInfo myTimer, ILogger log)
        {
            log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");

            //Query using Graph SDK (preferred when possible)
            GraphServiceClient graphClient = GetAuthenticatedGraphClient();
            List<QueryOption> options = new List<QueryOption>
            {
                new QueryOption("$top", "1")
            };

            var graphResult = graphClient.Users.Request(options).GetAsync().Result;
            log.LogInformation("Graph SDK Result");
            log.LogInformation(graphResult[0].DisplayName);
        }

        private static GraphServiceClient GetAuthenticatedGraphClient()
        {
            var authenticationProvider = CreateAuthorizationProvider();
            _graphServiceClient = new GraphServiceClient(authenticationProvider);
            return _graphServiceClient;
        }

        private static IAuthenticationProvider CreateAuthorizationProvider()
        {
            var clientId = System.Environment.GetEnvironmentVariable("AzureADAppClientId", EnvironmentVariableTarget.Process);
            var clientSecret = System.Environment.GetEnvironmentVariable("AzureADAppClientSecret", EnvironmentVariableTarget.Process);
            var redirectUri = System.Environment.GetEnvironmentVariable("AzureADAppRedirectUri", EnvironmentVariableTarget.Process);
            var tenantId = System.Environment.GetEnvironmentVariable("AzureADAppTenantId", EnvironmentVariableTarget.Process);
            var authority = $"https://login.microsoftonline.com/{tenantId}/v2.0";

            //this specific scope means that application will default to what is defined in the application registration rather than using dynamic scopes
            List<string> scopes = new List<string>();
            scopes.Add("https://graph.microsoft.com/.default");

            var cca = ConfidentialClientApplicationBuilder.Create(clientId)
                                              .WithAuthority(authority)
                                              .WithRedirectUri(redirectUri)
                                              .WithClientSecret(clientSecret)
                                              .Build();

            return new MsalAuthenticationProvider(cca, scopes.ToArray());;
        }
    }

Test Azure Function

Test out the Azure Function by doing the following:

  1. Run “dotnet build” from the terminal.
  2. Assuming the build is successful you can then execute the Azure Function locally by pressing the F5 key.

See below for sample output.

CSAdvent-MSGAZFunc12

Deploy Azure Function

The Azure Functions extension for Visual Studio Code makes the deployment process much easier.  Search for “Azure Functions: Deploy to Function App…” and select it.

CSAdvent-MSGAZFunc9

I already have a consumption based, Windows hosted Function App deployed in my Azure subscription but you can also create a new one if needed.  Select the subscription and Function App to deploy to.

Upload App Settings

When you develop the Azure Function locally you make use of the local.settings.json file which is automatically loaded as environment variables.  In Azure though you will need to create those configuration values as app settings on the Function App.  Rather than manually add / edit those values, search for “Azure Functions: Upload Local Settings…” and select it.  This will upload any values you have in your local.settings.json as app settings on the Function App.

IMPORTANT: If you specified the AzureWebJobsStorage key to use local storage, do not overwrite that value in Azure.  Instead use the cloud Azure storage account already specified.

CSAdvent-MSGAZFunc10

(Bonus) Add Key Vault Integration for Client Secret

While it is possible to specify the Azure AD app secret in the Function App configuration settings (stored encrypted), anyone with read / edit access to the Function App will be able to view that value as plain text through the Azure portal.  As such it is recommended to store the value in Azure Key Vault or similar location.  To specify a location in Azure Key Vault following the documentation on Use Key Vault references for App Service and Azure Functions.

The reference in your Function App configuration will now look something like the following:

@Microsoft.KeyVault(SecretUri=https://yourvault.vault.azure.net/secrets/yoursecret/ec96f02080254f109c51a1f14cdb1931)

 

Conclusion

In this blog post we walked through the process to create, test, and deploy a C# compiled Azure Function that calls Microsoft Graph.  The sample repo can be found on GitHub at BTJ.CSAdvent.AZFunc.  Thank you for reading along and please open an issue on GitHub repo if you run into any issues with the sample project.  Enjoy the rest of The Third Annual C# Advent.

-Frog Out

Presenting at Collab365 SharePoint Summit 2019

I have the privilege of presenting “Getting Started with Microsoft Graph Development” at the upcoming Collab365 SharePoint Summit taking place Sept 10-12, 2019.  This is a free online conference with MVPs and experts from around the world presenting on developer, IT Pro, and adoption topics.

Title: Getting Started with Microsoft Graph Development

Abstract: “I hear that I need to use Microsoft Graph for developing against Office 365 but I have no clue where to start.” “I want to grant access to company data without throwing in the entire kitchen sink.” Fear not fellow developers and admins.

This session we will ramp you up to a 200 level knowledge on the pertinent parts of Microsoft Graph including endpoints available, syntax, authentication flows, and more. We will also cover useful examples of what can be accomplished using these APIs. Prior experience with Microsoft Graph is not required but can be helpful.

You can also purchase an all-access pass which includes lifetime access to the videos, additional e-books, and more.  Looking forward to participating in this great event.