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

3 thoughts on “Creating a C# Azure Function to Call Microsoft Graph

  1. Hi Brian, thank you for this amazing article!
    I have a question though, what value do we need put “AzureADAppRedirectUri” in the json configuration?
    running in localhost as well as after deploying the app..

    Like

    • Glad you find it useful. In my example I used the Confidential Client auth flow (application permissions = no user context, just app-only). For this there is no interactive authentication flow and hence no need to redirect back to the application. Therefore you can put any value you want (or none I believe) for that variable value.

      Like

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 )

Facebook photo

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

Connecting to %s