Channel credentials

As the name implies, channel credentials are attached to the underlying gRPC channel. The standard form of channel credentials uses client certificate authentication. In this process, the client provides a TLS certificate when it's making the connection, and then the server verifies this before allowing any calls to be made.

You can combine channel credentials with call credentials to provide comprehensive security for a gRPC service. The channel credentials prove that the client application is allowed to access the service, and the call credentials provide information about the person who is using the client application.

Client certificate authentication works for gRPC the same way it works for ASP.NET Core. For more information, see Configure certificate authentication in ASP.NET Core.

For development purposes you can use a self-signed certificate, but for production you should use a proper HTTPS certificate signed by a trusted authority.

Add certificate authentication to the server

Configure certificate authentication both at the host level (for example, on the Kestrel server), and in the ASP.NET Core pipeline.

Configure certificate validation on Kestrel

You can configure Kestrel (the ASP.NET Core HTTP server) to require a client certificate, and optionally to carry out some validation of the supplied certificate, before accepting incoming connections. You do this in the CreateWebHostBuilder method of the Program class, rather than in Startup.

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            var serverCert = ObtainServerCertificate();
            webBuilder.UseStartup<Startup>()
                .ConfigureKestrel(kestrelServerOptions => {
                    kestrelServerOptions.ConfigureHttpsDefaults(opt =>
                    {
                        opt.ClientCertificateMode = ClientCertificateMode.RequireCertificate;

                        // Verify that client certificate was issued by same CA as server certificate
                        opt.ClientCertificateValidation = (certificate, chain, errors) =>
                            certificate.Issuer == serverCert.Issuer;
                    });
                });
        });

The ClientCertificateMode.RequireCertificate setting causes Kestrel to immediately reject any connection request that doesn't provide a client certificate, but this setting by itself won't validate a certificate that is provided. Add the ClientCertificateValidation callback to enable Kestrel to validate the client certificate at the point the connection is made, before the ASP.NET Core pipeline is engaged. (In this case, the callback ensures that it was issued by the same Certificate Authority as the server certificate.)

Add ASP.NET Core certificate authentication

The Microsoft.AspNetCore.Authentication.Certificate NuGet package provides certificate authentication.

Add the certificate authentication service in the ConfigureServices method, and add authentication and authorization to the ASP.NET Core pipeline in the Configure method.

public class Startup
{
    private readonly bool _isDevelopment;

    public Startup(IWebHostEnvironment env)
    {
        _isDevelopment = env.IsDevelopment();
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddAuthentication(CertificateAuthenticationDefaults.AuthenticationScheme)
            .AddCertificate(options =>
            {
                if (_isDevelopment)
                {
                    // DO NOT DO THIS IN PRODUCTION!
                    options.RevocationMode = X509RevocationMode.NoCheck;
                }
            });
        services.AddAuthorization();
        services.AddGrpc();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        app.UseRouting();

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

        app.UseEndpoints(endpoints => { endpoints.MapGrpcService<GreeterService>(); });
    }
}

Provide channel credentials in the client application

With the Grpc.Net.Client package, you configure certificates on an HttpClient instance that is provided to the GrpcChannel used for the connection.

class Program
{
    static async Task Main(string[] args)
    {
        // Assume path to a client .pfx file and password are passed from command line
        // On Windows this would probably be a reference to the Certificate Store
        var cert = new X509Certificate2(args[0], args[1]);

        var handler = new HttpClientHandler();
        handler.ClientCertificates.Add(cert);
        var httpClient = new HttpClient(handler);

        var channel = GrpcChannel.ForAddress("https://localhost:5001/", new GrpcChannelOptions
        {
            HttpClient = httpClient
        });

        var grpc = new Greeter.GreeterClient(channel);
        var response = await grpc.SayHelloAsync(new HelloRequest { Name = "Bob" });
        System.Console.WriteLine(response.Message);
    }
}

Combine ChannelCredentials and CallCredentials

You can configure your server to use both certificate and token authentication. Do this by applying the certificate changes to the Kestrel server, and using the JWT bearer middleware in ASP.NET Core.

To provide both ChannelCredentials and CallCredentials on the client, use the ChannelCredentials.Create method to apply the call credentials. You still need to apply certificate authentication by using the HttpClient instance. If you pass any arguments to the SslCredentials constructor, the internal client code throws an exception. The SslCredentials parameter is only included in the Grpc.Net.Client package's Create method to maintain compatibility with the Grpc.Core package.

var handler = new HttpClientHandler();
handler.ClientCertificates.Add(cert);

var httpClient = new HttpClient(handler);

var callCredentials = CallCredentials.FromInterceptor(((context, metadata) =>
    {
        metadata.Add("Authorization", $"Bearer {_token}");
    }));

var channelCredentials = ChannelCredentials.Create(new SslCredentials(), callCredentials);

var channel = GrpcChannel.ForAddress("https://localhost:5001/", new GrpcChannelOptions
{
    HttpClient = httpClient,
    Credentials = channelCredentials
});

var grpc = new Portfolios.PortfoliosClient(channel);

Tip

You can use the ChannelCredentials.Create method for a client without certificate authentication. This is a useful way to pass token credentials with every call made on the channel.

A version of the FullStockTicker sample gRPC application with certificate authentication added is on GitHub.