1

I have a service and client gRPC service.

In my client application, I would like to include a client certificate, so only this application could call to the server.

The reason for that it is to avoid another person could develop an application an call to my service.

Also I want to use JWT token to identify each user, because it user will have different permissions.

My gRPC service is hosted in a ASP Core application,using .NET 7, and this is the program.cs file:

Environment.CurrentDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!;


var builder = WebApplication.CreateBuilder(args);


var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();



try
{
    builder.WebHost.ConfigureKestrel((context, options) =>
    {
        string miStrCertificado = File.ReadAllText(builder.Configuration.GetSection("Certificados:Certificado").Value!);
        string miStrKey = File.ReadAllText(builder.Configuration.GetSection("Certificados:Key").Value!);
        X509Certificate2 miCertficadoX509 = X509Certificate2.CreateFromPem(miStrCertificado, miStrKey);

        X509Certificate2 miCertificado2 = new X509Certificate2(miCertficadoX509.Export(X509ContentType.Pkcs12));

        miCertficadoX509.Dispose();

        options.ListenAnyIP(Convert.ToInt32(builder.Configuration.GetSection("Servidor:Puerto").Value!), listenOptions =>
        {
            listenOptions.Protocols = HttpProtocols.Http2;
            listenOptions.UseHttps(miCertificado2);
        });


        options.ConfigureHttpsDefaults(miHttpsOptions =>
        {
            miHttpsOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
            miHttpsOptions.ServerCertificate = miCertificado2;
        });
    });




    builder.Services.AddHttpContextAccessor();

    

    builder.Services.AddGrpc();
    builder.Services.AddCodeFirstGrpc();



    builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.Default.GetBytes(builder.Configuration.GetSection("JwtAuthApp.Server:JwtTokenService:ClaveCifradoToken").Value!)),
                RequireExpirationTime = true,
                RequireSignedTokens = true,
                ClockSkew = TimeSpan.FromSeconds(10),

                ValidateIssuer = false,
                ValidateAudience = false,
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
            };
        });


    builder.Services.AddAuthorization();





    var app = builder.Build();

    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.MapGrpcService<MyGrpcService>();




    await app.RunAsync();
}
catch (Exception ex)
{
    Console.Write($"ERROR: {ex.Message}");
}

How it is shown, I have configure the kestrel server, in the ConfigureHttpsDefaults section, to require a client certificate.

However, when I create the client, I don't include any certificate, and the client get repose from the service. I would expect I get an authentication exception.

Perhaps is it not possible to use two authentication options, in this case certificate and JWT token?

Thanks.

Álvaro García
  • 18,114
  • 30
  • 102
  • 193
  • If you execute some logic when verifying the certificate like [this](https://stackoverflow.com/a/68156897/18789859), such as printing some logs, will it be executed? It looks like you're currently only using JWT validation, I'm not sure if that could be the cause of this. – Chen May 01 '23 at 09:38

1 Answers1

0

I found the solution. I will share here, instead to modify the original question, to avoid to mix things.

There are two posible solutions, but both happens because of the same reason. This is because of the order in which I am configure the server.

In this case, it is important the order of the configuration in the kestrel configuration.

First solution: configure ConfigureHttpsDefaults before LinstenAnyIP:

builder.WebHost.ConfigureKestrel((context, options) => { string miStrCertificado = File.ReadAllText(builder.Configuration.GetSection("Certificados:Certificado").Value!); string miStrKey = File.ReadAllText(builder.Configuration.GetSection("Certificados:Key").Value!); X509Certificate2 miCertficadoX509 = X509Certificate2.CreateFromPem(miStrCertificado, miStrKey);

X509Certificate2 miCertificado2 = new X509Certificate2(miCertficadoX509.Export(X509ContentType.Pkcs12));

miCertficadoX509.Dispose();

options.ConfigureHttpsDefaults(miHttpsOptions =>
{
    miHttpsOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
    miHttpsOptions.ServerCertificate = miCertificado2;
});

options.ListenAnyIP(Convert.ToInt32(builder.Configuration.GetSection("Servidor:Puerto").Value!), listenOptions =>
{
    listenOptions.Protocols = HttpProtocols.Http2;
    listenOptions.UseHttps(miCertificado2);
});

});

Second option: configure the https options in the method UseHttps() inside ListenAnyIP:

builder.WebHost.ConfigureKestrel((context, options) =>
{
    string miStrCertificado = File.ReadAllText(builder.Configuration.GetSection("Certificados:Certificado").Value!);
    string miStrKey = File.ReadAllText(builder.Configuration.GetSection("Certificados:Key").Value!);
    X509Certificate2 miCertficadoX509 = X509Certificate2.CreateFromPem(miStrCertificado, miStrKey);

    X509Certificate2 miCertificado2 = new X509Certificate2(miCertficadoX509.Export(X509ContentType.Pkcs12));

    miCertficadoX509.Dispose();



    options.ListenAnyIP(Convert.ToInt32(builder.Configuration.GetSection("Servidor:Puerto").Value!), misOpcionesListener =>
    {
        misOpcionesListener.Protocols = HttpProtocols.Http2;

        misOpcionesListener.UseHttps(misOpcionesHttps =>
        {
            misOpcionesHttps.ServerCertificate = miCertificado2;
            misOpcionesHttps.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
            misOpcionesHttps.ClientCertificateValidation = ValidarCertificadoCliente!;
        });
    });
});

I hope this could help to another people that could have the same problem.

Thanks.

Álvaro García
  • 18,114
  • 30
  • 102
  • 193