DEV Community

Cover image for Securely accessing MS Graph from gRPC .NET with Azure AD
Christos Matskas for The 425 Show

Posted on

Securely accessing MS Graph from gRPC .NET with Azure AD

A few weeks ago (time flies by!!) I posted a blog post about securing .NET gRPC services using the Microsoft Identity platform, a.k.a Azure Active Directory. You can read the blog post here.

Today, I decided to take this one step further and extend the service to pull data from Microsoft Graph. The updated project comes with a slew of changes as many things changed since the last blog post:

  1. Microsoft.Identity.Web, the authentication and token management library hit GA and there have been some API changes.
  2. The API changes include tight integration with Microsoft Graph which is now supported out of the box with a new NuGet package.

You can read all about these changes on our GA announcement blog post.

And if you're wondering why should you use gRPC over normal APIs that parse JSON, then this tweet from James Netwon-King (the author of Newtonsoft.json) should help put things into perspective:

Let's write some code!

Enough talking, let's get to coding. Open the previous gRPC project (clone from here) and let's get cracking. First, in our grpcWithAuth project we need to update the NuGet packages. I like to mess with my *.csproj files so this is what my XML looks like after updating the packages to the latest version:

<ItemGroup>
    <PackageReference Include="Grpc.AspNetCore" Version="2.27.0" />
    <PackageReference Include="Microsoft.Identity.Web" Version="1.1.0" />
    <PackageReference Include="Microsoft.Identity.Web.MicrosoftGraph" Version="1.1.0" />
</ItemGroup>

Since we will be interacting with the MS Graph, we need to create a new proto file to define the service and messages. In the Protos folder, add a new file graph.proto and paste the following content:

syntax = "proto3";

option csharp_namespace = "grpcWithAuth";

package graph;

service GraphService {
    // Sends the calendar info from Graph
    rpc GetCalendar (CalendarRequest) returns (CalendarReply);
  }

  // The request message
  message CalendarRequest {
    string name = 1;
  }

  // The response message containing the calendar information.
  message CalendarReply {
    string subject = 1;
    string bodyPreview = 2;
    string start = 3;
    string end = 4;
  }

Next, we need to add a new service to handle the incoming requests. In the Services folder add a new GraphAPIService.cs class. The code for this class is provided below:

using System;
using System.Linq;
using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Logging;
using Microsoft.Graph;
using Microsoft.Identity.Web.Resource;

namespace grpcWithAuth
{
    public class GraphAPIService : GraphService.GraphServiceBase
    {
        private static readonly string[] requiredScope = new string[] {"access_as_user"};
        private readonly ILogger<GraphAPIService> _logger;
        private readonly GraphServiceClient _graphServiceClient;
        public GraphAPIService(ILogger<GraphAPIService> logger, GraphServiceClient graphServiceClient)
        {
            _logger = logger;
            _graphServiceClient = graphServiceClient;
        }

        [Authorize]
        public override async Task<CalendarReply> GetCalendar(CalendarRequest request, ServerCallContext context)
        {
            var httpContext = context.GetHttpContext();
            httpContext.VerifyUserHasAnyAcceptedScope(requiredScope);

            if (!request.Name.Equals("Christos", StringComparison.OrdinalIgnoreCase))
            {
                // search the Graph for the user's calendar data
                // to-do
                return new CalendarReply();
            }

            var data = await _graphServiceClient.Me.Events
                .Request()
                .Select("subject,bodyPreview,start,end")
                .GetAsync();

            return new CalendarReply
            {
                Subject = data.First().Subject,
                BodyPreview = data.First().BodyPreview,
                Start = data.First().Start.DateTime,
                End = data.First().End.DateTime
            };
        }
    }
}

Now, let's update our Startup.cs to handle the token validation and include the Graph client. Since the Microsoft.Identity.Web integrates with Graph via the Microsoft.Identity.Web.Graph package, it's now a lot more straightforward to make the Graph client available in our app. Update the ConfigureServices() method with the following code:

services.AddGrpc();
services.AddMicrosoftIdentityWebApiAuthentication(Configuration)
        .EnableTokenAcquisitionToCallDownstreamApi()
     .AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
        .AddInMemoryTokenCaches();

services.AddAuthorization();

Update the gRCP Service App Registration in AAD

There are 2 more steps to complete this task. Firstly, we must update the App Registration in AAD to include the Graph API permissions. Secondly we need to update the appsettings.json file in our app so that we can call the downstream API (i.e Graph).

In the Azure AD portal, go to the App Registrations tab and find the gRPC Service app registration. Navigate to the Certificates & Secrets and create a new secret:

Alt Text

Next, we need to add the appropriate Graph API permission, i.e Calendars.Read. Navigate to the API Permissions tab and press the Add a permission button:
Alt Text

Next, select Delegated permissions, search for Calendars.Read and press the Add permissions button to persist the changes

Alt Text

The final step is to Grant admin consent for . Since this will be a Service-to-Service call, the user can't consent to these new permissions. Therefore, we need to pre-consent the new API permissions to allow the calls to succeed.

Alt Text

We don't need to update the gRPC Client app registration as nothing's changed for our client app in terms or permissions.

Updates to the gRPC Service appsettings.json

With our app registration updated, we now need to update the appsettings.json file in the gRPC service to make use of the new settings. Open the file and update or add the following sections:

 "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "cmatskas.onmicrosoft.com",
    "TenantId": "b55f0c51-0000-0000-0000-33569b247796",
    "ClientId": "e7ba8202-0000-0000-0000-90ee506bc5cf",
    "ClientSecret": "PxgK83FL-0000_aaa_0000000000000000"
  },
  "DownstreamAPI": {
    "BaseUrl": "https://graph.microsoft.com/v1.0",
    "Scopes": "user.read calendars.read"
  }

These are all the changes we need to do to our gRPCWitAuth, i.e the gRPC service to integrate with MS Graph.

Update the gRPC Client app

As with the previous blog post, we need a client app to consume the service. Our GitHub repo included a grpcClient console app that acts as our gRPC client.
The only step required here is to update the Main method to call the gRPC service using the new GraphService:

using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
using grpcWithAuth;
using Microsoft.Extensions.Configuration;

using static System.Console;

namespace grpcClient
{
    class Program
    {
        private static IConfiguration configuration;
        static async Task Main(string[] args)
        {
            LoadAppSettings();

            var authProvider = new DeviceCodeAuthProvider(configuration);
            var token = await authProvider.GetAccessToken(new string[] {configuration["scope"]});

            var channel = GrpcChannel.ForAddress("https://localhost:5001");
            var graphClient = new GraphService.GraphServiceClient(channel);

            var headers = new Metadata();
            headers.Add("Authorization", $"Bearer {token}");

            var calendarRequest = new CalendarRequest()
            {
                Name = "Christos"
            };

            var calendarResponse = await graphClient.GetCalendarAsync(calendarRequest, headers);
            WriteLine("*************** New event found ***************");
            WriteLine($"Calendar event subject: {calendarResponse.Subject}");
            WriteLine($"Calendar event body preview: {calendarResponse.BodyPreview}");
            WriteLine($"Calendar event start date/time: {calendarResponse.Start}");
            WriteLine($"Calendar event end date/time: {calendarResponse.End }");
        }

        static void LoadAppSettings()
        {
            configuration = new ConfigurationBuilder()
                            .SetBasePath(System.IO.Directory.GetCurrentDirectory())
                            .AddJsonFile("appsettings.json")
                            .Build();
        }
    }
}

Run it!!

Run the gRPCWithAuth service first. Then run the gRPCClient. Our console up will prompt for authentication. Follow the steps:

Alt Text

Alt Text

Alt Text

Alt Text

It worked! Now let's look at the service output and see what happened.

Alt Text

As you can see, the service received the incoming call, validates the access token, then authenticates for the downstream API (aka our MS Graph) and then executes the /GetCalendar request.

Alt Text

Give me teh codez

Alt Text

If you want to grab the code, jump straight to the GitHub repo. I have a new branch so that you can compare and contrast the old and the new branches.

One thing that you'll notice is different is that I've lately started adding Code Tours to the source code. This is an excellent code extension for VS Code that allows you to take a tour through the code and get explanations/annotation for code that is important to a task at hand. I would urge you to use it to understand how this project was put together and also to start using it yourselves to help other understand what you build

Alt Text

Summary

It was great fun integrating Microsoft.Identity.Web and expanding the service to include Microsoft Graph. As you may have alluded from this blog post, the new library makes it extremely straightforward to implement authentication and add MS Graph. I would encourage you to go ahead and start using the new library to your ASP.NET Core projects today.

And don't forget! You can catch me and JP on stream doing things like this. See you Twitch - https://aka.ms/425Show

Top comments (0)