Search code examples
asp.net-coreprotobuf-netblazor-webassemblyprotobuf-net.grpc

trying to Azure AD authentication with gRPC-Web using protobuf-net


I am trying to Azure AD authentication with gRPC-Web in a blazor webassembly app. I am using protobuf-net to help me with the serialization. I am not sure how to pass the token to have the server side recognize it. this is what I have:

var headers = new Metadata
               {
                 { "Authorization", $"Bearer {Token}" }
               };

and, I am sending that as a parameter in the method I want to consume

var result = await Client.CreateCustomer(this.customer, headers);

This is how the service is injected:

builder.Services.AddTransient(services =>
        {
            var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
            var channel = Grpc.Net.Client.GrpcChannel.ForAddress("****", new GrpcChannelOptions { HttpClient = httpClient });
            return channel.CreateGrpcService<Application.Services.ICustomerService<ServerCallContext>>();
        });

This is how the service is published:

endpoints.MapGrpcService<CustomerService>().RequireAuthorization().EnableGrpcWeb()

and, this is the implementation:

public class CustomerService : ICustomerService<ServerCallContext>
{
    [Authorize]
    public async ValueTask<Customer> CreateCustomer(Customer customerDTO, ServerCallContext context) 
    {****}
}

the error I am getting is cannot convert from 'Grpc.Core.Metadata' to 'Grpc.Core.ServerCallContext' which is kind of obvious.

The reference I have found uses Metadata but is ServerCallContext the one I am supposed to use https://learn.microsoft.com/en-us/dotnet/architecture/grpc-for-wcf-developers/metadata so what I am missing, what I am doing wrong, how to properly use both using protobuf-net?


Solution

  • It looks like the problem here is that you're using ServerCallContext in the method signature; the underlying gRPC core has separate client/server context APIs, but this is not amenable to use on an agnostic interface, and as such, protobuf-net.Grpc unifies these two APIs, via CallContext. So: instead of:

    async ValueTask<Customer> CreateCustomer(Customer customerDTO, ServerCallContext context)
    

    for the signature, consider:

    async ValueTask<Customer> CreateCustomer(Customer customerDTO, CallContext context)
    

    or

    async ValueTask<Customer> CreateCustomer(Customer customerDTO, CallContext context = default)
    

    The CallContext API exposes the common server-side and client-side APIs (headers, cancellation, etc) in a single way, or you can use (for example) context.ServerCallContext to get the server-specific API if needed (this will throw an exception if used on a client-context). For client-side usage, a CallContext can be constructed from a CallOptions, which is the core gRPC client-side API, for example:

    var result = await service.CreateCustomer(customer, new CallOptions(headers));
    

    I'm open to the idea of allowing CallContext to be created directly from Metadata / CancellationToken etc (allowing var result = await service.CreateCustomer(customer, headers);) - but it doesn't seem essential.