Autenticación y autorización en gRPC para ASP.NET Core

Por James Newton-King

Vea o descargue el código de ejemplo (cómo descargarlo)

Autenticación de los usuarios que llaman a un servicio gRPC

gRPC se puede usar con la autenticación de ASP.NET Core para asociar un usuario a cada llamada.

El siguiente es un ejemplo de Startup.Configure en el que se usa gRPC y la autenticación de ASP.NET Core:

public void Configure(IApplicationBuilder app)
{
    app.UseRouting();
    
    app.UseAuthentication();
    app.UseAuthorization();

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

Nota

El orden en el que se registra el middleware de autenticación de ASP.NET Core es importante. Llame siempre a UseAuthentication y UseAuthorization después de UseRouting y antes de UseEndpoints.

Es necesario configurar el mecanismo de autenticación que usa la aplicación durante una llamada. La configuración de autenticación se agrega en Startup.ConfigureServices y será diferente en función del mecanismo de autenticación que use la aplicación. Para obtener ejemplos de cómo proteger aplicaciones ASP.NET Core, vea Ejemplos de autenticación.

Una vez que se ha configurado la autenticación, se puede acceder al usuario en los métodos de un servicio gRPC mediante ServerCallContext.

public override Task<BuyTicketsResponse> BuyTickets(
    BuyTicketsRequest request, ServerCallContext context)
{
    var user = context.GetHttpContext().User;

    // ... access data from ClaimsPrincipal ...
}

Autenticación por token de portador

El cliente puede proporcionar un token de acceso para la autenticación. El servidor valida el token y lo usa para identificar al usuario.

En el servidor, la autenticación de token de portador se configura mediante el middleware de portador JWT.

En el cliente gRPC de .NET, el token se puede enviar con llamadas usando la colección Metadata. Las entradas de la colección Metadata se envían con una llamada gRPC como encabezados HTTP:

public bool DoAuthenticatedCall(
    Ticketer.TicketerClient client, string token)
{
    var headers = new Metadata();
    headers.Add("Authorization", $"Bearer {token}");

    var request = new BuyTicketsRequest { Count = 1 };
    var response = await client.BuyTicketsAsync(request, headers);

    return response.Success;
}

La configuración de ChannelCredentials en un canal es una forma alternativa de enviar el token al servicio con llamadas a gRPC. Un elemento ChannelCredentials puede incluir CallCredentials, que proporcionan una manera de establecer Metadata de forma automática.

CallCredentials se ejecuta cada vez que se realiza una llamada gRPC, lo que evita la necesidad de escribir código en varios lugares para pasar el token personalmente. Tenga en cuenta que CallCredentials solo se aplican si el canal está protegido con TLS. CallCredentials no se aplican en los canales sin protección que no son TLS.

La credencial del ejemplo siguiente configura el canal para enviar el token con cada llamada a gRPC:

private static GrpcChannel CreateAuthenticatedChannel(string address)
{
    var credentials = CallCredentials.FromInterceptor((context, metadata) =>
    {
        if (!string.IsNullOrEmpty(_token))
        {
            metadata.Add("Authorization", $"Bearer {_token}");
        }
        return Task.CompletedTask;
    });

    // SslCredentials is used here because this channel is using TLS.
    // CallCredentials can't be used with ChannelCredentials.Insecure on non-TLS channels.
    var channel = GrpcChannel.ForAddress(address, new GrpcChannelOptions
    {
        Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)
    });
    return channel;
}

Token de portador con la fábrica de cliente de gRPC

La fábrica de cliente de gRPC puede crear clientes que envíen un token de portador mediante ChannelCredentials. Al configurar un cliente, asigne el elemento CallCredentials que el cliente debe usar con el método ConfigureChannel.

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .ConfigureChannel(o =>
    {
        var credentials = CallCredentials.FromInterceptor((context, metadata) =>
        {
            if (!string.IsNullOrEmpty(_token))
            {
                metadata.Add("Authorization", $"Bearer {_token}");
            }
            return Task.CompletedTask;
        });

        o.Credentials = ChannelCredentials.Create(new SslCredentials(), credentials);
    });

Autenticación de certificados de clientes

Un cliente podría proporcionar alternativamente un certificado de cliente para la autenticación. La autenticación de certificados se realiza en el nivel de TLS, mucho antes de que llegue a ASP.NET Core. Cuando la solicitud entra en ASP.NET Core, el paquete de autenticación de certificado de cliente le permite resolver el certificado en un elemento ClaimsPrincipal.

Nota

Configure el servidor para que acepte certificados de cliente. Para obtener información sobre la aceptación de certificados de cliente en Kestrel, IIS y Azure, vea Configuración de la autenticación de certificados en ASP.NET Core.

En el cliente gRPC de .NET, el certificado de cliente se agrega a HttpClientHandler, que después se usa para crear el cliente gRPC:

public Ticketer.TicketerClient CreateClientWithCert(
    string baseAddress,
    X509Certificate2 certificate)
{
    // Add client cert to the handler
    var handler = new HttpClientHandler();
    handler.ClientCertificates.Add(certificate);

    // Create the gRPC channel
    var channel = GrpcChannel.ForAddress(baseAddress, new GrpcChannelOptions
    {
        HttpHandler = handler
    });

    return new Ticketer.TicketerClient(channel);
}

Otros mecanismos de autenticación

Muchos mecanismos de autenticación admitidos por ASP.NET Core funcionan con gRPC:

  • Azure Active Directory
  • Certificado de cliente
  • IdentityServidor
  • Token de JWT
  • OAuth 2.0
  • OpenID Connect
  • WS-Federation

Para obtener más información sobre la configuración de la autenticación en el servidor, vea Autenticación de ASP.NET Core.

La configuración del cliente gRPC para usar la autenticación dependerá del mecanismo de autenticación que se use. En los ejemplos anteriores de token de portador y certificado de cliente se muestran un par de formas de configurar el cliente gRPC para enviar metadatos de autenticación con llamadas a gRPC:

  • Los clientes gRPC fuertemente tipados usan HttpClient internamente. La autenticación se puede configurar en HttpClientHandler o mediante la adición de instancias de HttpMessageHandler personalizadas a HttpClient.
  • Cada llamada a gRPC tiene un argumento CallOptions opcional. Se pueden enviar encabezados personalizados mediante la colección de encabezados de la opción.

Nota

La autenticación de Windows (NTLM/Kerberos/Negotiate) no se puede usar con gRPC. gRPC requiere HTTP/2 y HTTP/2 no admite la autenticación de Windows.

Autorización a los usuarios para acceder a servicios y métodos de servicio

De forma predeterminada, los usuarios no autenticados pueden llamar a todos los métodos de un servicio. Para requerir la autenticación, aplique el atributo [Authorize] al servicio:

[Authorize]
public class TicketerService : Ticketer.TicketerBase
{
}

Puede usar los argumentos de constructor y las propiedades del atributo [Authorize] para restringir el acceso solo a los usuarios que coincidan con directivas de autorización específicas. Por ejemplo, si tiene una directiva de autorización personalizada llamada MyAuthorizationPolicy, asegúrese de que solo los usuarios que coincidan con esa directiva puedan acceder al servicio mediante el código siguiente:

[Authorize("MyAuthorizationPolicy")]
public class TicketerService : Ticketer.TicketerBase
{
}

A los métodos de servicio individuales también se les puede aplicar el atributo [Authorize]. Si el usuario actual no coincide con las directivas aplicadas tanto al método como a la clase, se devuelve un error al autor de la llamada:

[Authorize]
public class TicketerService : Ticketer.TicketerBase
{
    public override Task<AvailableTicketsResponse> GetAvailableTickets(
        Empty request, ServerCallContext context)
    {
        // ... buy tickets for the current user ...
    }

    [Authorize("Administrators")]
    public override Task<BuyTicketsResponse> RefundTickets(
        BuyTicketsRequest request, ServerCallContext context)
    {
        // ... refund tickets (something only Administrators can do) ..
    }
}

Recursos adicionales