/ CODING, ASPNETCORE, GRPC

Use Azure AD authentication with gRPC.

TL;DR

Have a look at this example on GitHub for an example application using ASP.NET core 3.0 to host a gRPC service that uses AzureAD for authentication.

Background

I wanted to look into gRPC for a while now, so during our innovation day at Xpirit last Friday my colleague Marc Bruins and I took our chance and explored what gRPC is, how to use it with ASP.NET core, and how you can Azure AD authentication to it. What gRPC is and how you can use it with ASP.NET core is pretty well documented, so instead the focus is on how to add Azure AD authentication to it. Spoiler alert: it is straightforward, it was just hard to find good examples.

By default, gRPC runs on top of HTTP2, and it is essential to keep that in mind. It took us a while to figure out how to add authentication to gRPC, but once we realized it is “just HTTP” it got a lot easier. In the end, adding authentication with JWT tokens to a gRPC server is as simple as sending an Authorization header with your JWT token and wiring up the correct ASP.NET core middleware on the server just as you would do for a regular HTTP API.

Currently, grpc-dotnet is adding first class support for gRPC to ASP.NET Core 3. There is even a project template for gRPC available now as part of the .NET core 3.0 preview SDK. We will use this template as base and add authtication to it.

Create a gRPC service

To get started, create a new ASP.NET core 3.0 preview project and select the gRPC Service template. To be able to use Azure AD based authentication, add these 2 packages to your server project:

<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="3.0.0-preview3-19153-02" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0-preview3-19153-02" />

The configuration for Azure AD is read from the appsettings.json, so we need access to IConfiguration. To enable this add this constructor and public property to Startup.cs:

//......
public Startup(IConfiguration configuration)
{
    Configuration = configuration;
}

public IConfiguration Configuration { get; }
//......

We can use the “normal” ASP.NET core authentication and authorization functionality, just as we would for an HTTP REST API. To configure this change the ConfigureServices method in Startup.cs to this:

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpContextAccessor();

    services.AddAuthorization();
    services.AddAuthorizationPolicyEvaluator();
    services.AddAuthentication(AzureADDefaults.BearerAuthenticationScheme)
      .AddAzureADBearer(options => Configuration.Bind("AzureAd", options));

    services.AddGrpc(options =>
    {
        options.EnableDetailedErrors = true;
    });
}

To configure authorization for the gRPC service endpoint, add .RequireAuthorization() to the route configuration. Also, add the authentication and authorization middleware. To enable this, in Startup.cs change the Configure method to this:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting(routes => {
      routes.MapGrpcService<ContentStoreImpl>()
        .RequireAuthorization();
      }
    );

    app.UseAuthentication();
    app.UseAuthorization();
}

Now configure the required Azure AD settings in appsettings.json file:

{
  //........
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "sanderaernoutssupersecret.onmicrosoft.com",
    "TenantId": "67fa4d25-d682-4fc0-827f-fe0a3dc07fbf",
    "ClientId": "bb063c8e-0600-4627-8927-ae6e7fa2d5a7"
  }
  //........
}

Change GreeterService.cs to this (add anAuthorize attribute):

[Authorize]
public class GreeterService : Greeter.GreeterBase
{
    public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
    {
        return Task.FromResult(new HelloReply
        {
            Message = "Hello " + request.Name
        });
    }
}

Finally in change the client to send a Authorize HTTP header with a JWT token to the server:

//.....
var token = "<token>";
var metadata = new Metadata
{
    { "Authorization", $"Bearer {token}"}
});

var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" }, metadata);
//....

Let’s add the name of the IUserIdentity to the message to prove we have authenticated with an Azure AD user.

namespace GrpcAuthDemo
{
    [Authorize]
    public class GreeterService : Greeter.GreeterBase
    {
        public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
        {
            var httpContenxt = context.GetHttpContext();
            var user = httpContenxt.User;
            return Task.FromResult(new HelloReply
            {
                Message = $"Hello {request.Name} (logged in as: {user.Identity.Name})"
            });
        }
    }
}

Now run the example (with a valid JWT token), and you should see something like this:

Greeting: Hello GreeterClient (logged in as: live.com#sander.aernouts@supersecret.com)

Note that the client connect over plain HTTP instead of HTTPS(an SSL encrypted connection), which is fine for our localhost example. See this section of the gRPC documentation to setup SSL encryption.

Credits

Cover photo by unsplash-logoDonald Giannatti