Search code examples
c#asp.net-corejwtprotobuf-netprotobuf-net.grpc

How can I securize my code-first gRPC endpoints using Azure Authentication?


I'm building a microservice. I have some working REST endpoints securized by JWT generated by Microsoft Azure. Here is how the token is verified :

builder.Services
    .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(options =>
    {
        builder.Configuration.Bind("Authorization:AzureAd", options);
        options.UseSecurityTokenValidators = true;
    },
    options =>
    {
        builder.Configuration.Bind("Authorization:AzureAd", options);
    });
builder.Services.AddAuthorization();

I get a JWT from Postman using my Azure account. For example, when I use my token in Swagger it works (200) otherwise, without a valid token, I receive a 401 as expected.

Now I'm trying to integrate gRPC endpoints with the exact same security.

The idea is to use protobuf-net.Grpc to develop the endpoints using the code-first approach, and provides .proto files to my clients, which should work using the regular contract-first approach with the Grpc.AspNetCore library.

First, here is the interface :

[ServiceContract]
public interface IGreeterServiceContract
{
    [OperationContract]
    Task<GreeterResponse> SayHelloAsync(GreeterRequest request, CallContext context = default);
}

Then the implementation, with the [Authorize] attribute :

public class ServerGreeterService(IGreeterService _greeterService) : IGreeterServiceContract
{
    [Authorize]
    public Task<GreeterResponse> SayHelloAsync(GreeterRequest request, CallContext context = default)
    {
        string result = _greeterService.SayHello(request.Name);
        GreeterResponse response = new GreeterResponse { Message = result };
        return Task.FromResult(response);
    }
}

Note that this endpoint is correctly called. If I remove the [Authorize] attribute, it works as expected (but obviously without any security check).

Using the ProtoBuf.Grpc.Reflection, I generate the following .proto files :

syntax = "proto3";
package TestGrpc.Services.Abstractions;

message GreeterRequest {
   string Name = 1;
}
message GreeterResponse {
   string Message = 1;
}
service GreeterServiceContract {
   rpc SayHello (GreeterRequest) returns (GreeterResponse);
}

Then I create a new client solution and generate my client service from the previous .proto file. Here is how I register the client in the DI :

builder.Services.AddGrpc();
builder.Services.AddScoped<IGreeterService, GreeterService>();

builder.Services.AddGrpcClient<GreeterServiceContractClient>(o =>
{
    o.Address = new Uri("http://localhost:5001");
})
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
    ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
})
.ConfigureChannel(o =>
{
    o.Credentials = Grpc.Core.ChannelCredentials.Insecure;
    o.UnsafeUseInsecureChannelCallCredentials = true; // REMOVE FROM PRODUCTION
})
.AddCallCredentials((context, metadata, serviceProvider) =>
{
    var httpContext = serviceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext;
    if (httpContext != null && httpContext.Request.Headers.TryGetValue("Authorization", out var authHeader))
    {
        var token = authHeader.ToString().Split(" ").Last();
        if (!string.IsNullOrEmpty(token))
        {
            metadata.Add("Authorization", $"Bearer {token}");
        }
    }
    return Task.CompletedTask;
});

This code seems functional. I create a REST endpoint protected by an [Authorize] attribute, and this endpoint make a call to the other microservice using gRPC to get the result of the GreeterService. The metadata.Add() is called whenever the client is injected, and the token is correctly retrieved (and should be passed to the gRPC client).

But the server returns a 401 (unauthenticated).

At this point I'm not able to verify the metadata sent from the client, or received by the server. Maybe the metadata aren't even passed to the server, or not received.

Reading the documentation of protobuf-net, I can't find an example using the Azure JWT verification. But I guess that the mecanism is very similar.

What am I missing here ? How can I securize my code-first endpoints using Azure Authentication ?


Solution

  • I had to remove the app.UseHttpsRedirection(); to be able to call a non-TLS secured gRPC endpoint.