Microsoft.Graph and Application Authentication

Microsoft.Graph is modern and recommended approach to integrate with Microsoft 365 that includes Calendar, Mail, Online Meetings and many more. There two types of workflows that we can implement:

  • When we have user online and can get user consent to perform certain action. This type of workflow will most likely have a user interacting with website or UI.
  • When we do background work or want to act on behalf of a user or cannot get user’s consent. This type does not require user to be online and can perform actions on behalf of a user given we have admin’s consent to perform such actions.

In this article we are going to focus on later.

Preparation

As first thing we need to register an Application. Application will be used as integration point with Microsoft.Graph. Detailed instructions can be found here, however the process boils down to three steps:

  • Register an Application.
  • Grant API access. In this step as administrator grants application access to certain API and also provide admin consent.
  • Create client secret. Keep secret in a secure place.

Packages

Once we have all of the above completed, we can begin writing code. So let’s create our new library

mkdir MsGraphDemo
dotnet new classlib

Now we need to install nuget packages that we are going to use to access Microsoft.Graph.

Install Microsoft.Graph

cd MsGraphDemo
dotnet add package Microsoft.Graph --version 3.23.0

For authentication we need Microsoft.Identity.Client

dotnet add package Microsoft.Identity.Client --version 4.25.0

And to glue Microsoft.Graph and Microsoft.Identity.Client we need to install Microsoft.Graph.Auth.

dotnet add package Microsoft.Graph.Auth --version 1.0.0-preview.6

Configuration

We will have to supply multiple parameters to Microsoft.Graph library, therefore it makes sense to combine all of them into a class GraphConfig.

using System;
using System.Globalization;

namespace MicrosoftGraphApplicationAuth
{
    public class GraphConfig : IGraphConfig
    {
        private const string InstanceTemplate = "https://login.microsoftonline.com/{0}";

        public string ApiUri => "https://graph.microsoft.com";

        public string ApplicationId => "TODO: PUT APPLICATION ID HERE";

        public string Tenant => "TODO: PUT TENANT ID HERE";

        public string ClientId => "TODO: PUT CLIENT ID HERE";

        public string ClientSecret => "TODO: RETURN SECRET FROM HERE";

        public Uri Authority => new Uri(string.Format(CultureInfo.InvariantCulture, InstanceTemplate, Tenant));

        public string HttpProxyHost => string.Empty;

        public int? HttpProxyPort => 0;
    }
}

There are four most important values that you will need to provide.

  • ApplicationId. This is Application Object id that we can find when we register Application with Microsoft.Graph.
  • Tenant. It can be Azure Active Directory tenant id in which the organization is registered or domain name associated with the tenant. Tenant can also be found on application page.
  • ClientId. This is Guild used by the application to uniquely identify itself with Azure Active Directory. ClientId can be found on application registration page.
  • ClientSecret. This is the value we get when we create the secret.

If you have proxy, you can also provide HttpProxyHost and HttpProxyPort.

Create client

In order to communicate with Microsoft.Graph we need to create an instance of GraphServiceClient. The code can be found below; however, it is worth to mention that if you run behind a proxy there are more things involved. Luckily, we have code to cover proxy case as well. Let’s look at the simplest case when there is no proxy.

Client with no proxy

There are just few steps we need to take to build GraphServiceClient without proxy.

  • Build ConfidentialClientApplication using ConfidentialClientApplicationBuilder.
  • Create ClientCredentialProvider and then use the above to create GraphServiceClient.
using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;

namespace MicrosoftGraphApplicationAuth
{
    public class GraphServiceClientFactory : IGraphServiceClientFactory
    {
        public IGraphServiceClient Create(IGraphConfig graphConfig)
        {
            IConfidentialClientApplication clientApp = ConfidentialClientApplicationBuilder
                .Create(graphConfig.ClientId)
                .WithClientSecret(graphConfig.ClientSecret)
                .WithAuthority(graphConfig.Authority)
                .Build();
            var scope = $"{graphConfig.ApiUri}.default";
            return new GraphServiceClient(new ClientCredentialProvider(clientApp, scope));
        }
    }
}

Client with proxy support

If we have proxy, we need to configure Microsoft.Graph and Authentication requests to go through proxy, hence GraphServiceClient and ConfidentialClientApplication need to be configured accordingly. In order to do so we need to setup HttpClientHandler that will be using our proxy settings.

var httpClientHandlerWithProxy = new HttpClientHandler
{
    AllowAutoRedirect = false,
    AutomaticDecompression = DecompressionMethods.None,
    Proxy = new WebProxy(proxyHost, proxyPort)
};

ConfidentialClientApplication takes IMsalHttpClientFactory as argument, therefore we need to create a class, let’s say HttpClientFactoryWithProxy, that implements IMsalHttpClientFactory and return HttpClient that will be using previously created HttpClientHandler with proxy.

using System;
using System.Net;
using System.Net.Http;
using Microsoft.Identity.Client;

namespace MicrosoftGraphApplicationAuth
{
    internal class HttpClientFactoryWithProxy: IMsalHttpClientFactory, IDisposable
    {
        private readonly HttpClient _httpClient;

        public HttpClientFactoryWithProxy(string proxyHost, int proxyPort)
        {
            _httpClient = new HttpClient(CreateHttpClientHandler(proxyHost, proxyPort), true);
        }

        ~HttpClientFactoryWithProxy()
        {
            Dispose();
        }

        public void Dispose()
        {
            _httpClient?.Dispose();
            GC.SuppressFinalize(true);
        }

        public HttpClient GetHttpClient()
        {
            return _httpClient;
        }

        public static HttpClientHandler CreateHttpClientHandler(string proxyHost, int proxyPort)
        {
            return new HttpClientHandler
            {
                AllowAutoRedirect = false,
                AutomaticDecompression = DecompressionMethods.None,
                Proxy = new WebProxy(proxyHost, proxyPort)
            };
        }
    }
}

Now we can build ConfidentialClientApplication.

IConfidentialClientApplication clientApp = ConfidentialClientApplicationBuilder
    .Create(graphConfig.ClientId)
    .WithClientSecret(graphConfig.ClientSecret)
    .WithAuthority(graphConfig.Authority)
    .WithHttpClientFactory(new HttpClientFactoryWithProxy(
        graphConfig.HttpProxyHost,
        graphConfig.HttpProxyPort.Value))
    .Build();

We also need to pass similar HttpClientHandler to GraphServiceClient.

var scope = $"{graphConfig.ApiUri}.default";
var httpProvider = new HttpProvider(
    HttpClientFactoryWithProxy.CreateHttpClientHandler(
    graphConfig.HttpProxyHost, graphConfig.HttpProxyPort.Value),
    true);
return new GraphServiceClient(new ClientCredentialProvider(clientApp, scope), httpProvider);

Using GraphServiceClient

Now we have GraphServiceClient that can be used to talk to Microsoft.Graph. Let’s create a calendar event for a user.

using System;
using System.Threading.Tasks;
using Microsoft.Graph;

namespace MicrosoftGraphApplicationAuth
{
    public class ExampleUsingGraph
    {
        private readonly IGraphServiceClientFactory _factory;

        public ExampleUsingGraph(IGraphServiceClientFactory factory)
        {
            _factory = factory;
        }

        public async Task<Event> CreateCalendarEvent(string userEmail)
        {
            IGraphServiceClient client = _factory.Create(new GraphConfig());
            var tomorrow = DateTime.Now.AddDays(1);
            Event newEvvent = new Event
            {
                Subject = "Dinner Party",
                Start = DateTimeTimeZone.FromDateTime(tomorrow),
                End = DateTimeTimeZone.FromDateTime(tomorrow.AddHours(1))
            };
            return await client.Users[userEmail].Calendar.Events.Request().AddAsync(newEvvent);
        }
    }
}

Additional Resources

Posts created 20

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top