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 Program.cs en el que se usa gRPC y la autenticación de ASP.NET Core:

app.UseRouting();

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

app.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 Program.cs y será diferente en función del mecanismo de autenticación que use la aplicació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;
}

Establecimiento del token de portador con CallCredentials

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.

Ventajas de usar CallCredentials:

  • La autenticación se configura de forma centralizada en el canal. No es necesario proporcionar el token manualmente a la llamada de gRPC.
  • La devolución de llamada de CallCredentials.FromInterceptor es asincrónica. Las credenciales de llamada pueden capturar un token de credencial de un sistema externo si es necesario. Los métodos asincrónicos de la devolución de llamada deben usar CancellationToken en AuthInterceptorContext.

Nota

CallCredentials solo se aplican si el canal está protegido con TLS. El envío de encabezados de autenticación a través de una conexión no segura tiene implicaciones de seguridad y no debe realizarse en entornos de producción. Una aplicación puede configurar un canal para omitir este comportamiento y usar siempre CallCredentials estableciendo UnsafeUseInsecureChannelCallCredentials en un canal.

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

private static GrpcChannel CreateAuthenticatedChannel(ITokenProvder tokenProvider)
{
    var credentials = CallCredentials.FromInterceptor(async (context, metadata) =>
    {
        var token = await tokenProvider.GetTokenAsync(context.CancellationToken);
        metadata.Add("Authorization", $"Bearer {token}");
    });

    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 AddCallCredentials. Este método está disponible en Grpc.Net.ClientFactory, versión 2.46.0 o posterior.

El delegado pasado a AddCallCredentials se ejecuta para cada llamada de gRPC:

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

La inserción de dependencias (DI) se puede combinar con AddCallCredentials. Una sobrecarga pasa IServiceProvider al delegado, que se puede usar para obtener un servicio construido mediante inserción de dependencias usando servicios con ámbito y transitorios.

Imagine una aplicación que tiene:

  • Un ITokenProvider definido por el usuario para obtener un token de portador. ITokenProvider está registrado en la inserción de dependencias con una duración con ámbito.
  • El generador de clientes gRPC está configurado para crear clientes que se insertan en servicios gRPC y controladores de API web.
  • Las llamadas a gRPC deben usar ITokenProvider para obtener un token de portador.
public interface ITokenProvider
{
    Task<string> GetTokenAsync(CancellationToken cancellationToken);
}

public class AppTokenProvider : ITokenProvider
{
    private string _token;

    public async Task<string> GetTokenAsync(CancellationToken cancellationToken)
    {
        if (_token == null)
        {
            // App code to resolve the token here.
        }

        return _token;
    }
}
builder.Services.AddScoped<ITokenProvider, AppTokenProvider>();

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .AddCallCredentials(async (context, metadata, serviceProvider) =>
    {
        var provider = serviceProvider.GetRequiredService<ITokenProvider>();
        var token = await provider.GetTokenAsync(context.CancellationToken);
        metadata.Add("Authorization", $"Bearer {token}");
    }));

El código anterior:

  • Define ITokenProvider y AppTokenProvider. Estos tipos controlan la resolución del token de autenticación para las llamadas de gRPC.
  • Registra el tipo AppTokenProvider con DI en una duración con ámbito. AppTokenProvider almacena en caché el token de forma que solo se requiera la primera llamada en el ámbito para su cálculo.
  • Registra el tipo GreeterClient en el generador de clientes.
  • Configura AddCallCredentials para este cliente. El delegado se ejecuta cada vez que se realiza una llamada y agrega el token devuelto por ITokenProvider a los metadatos.

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 información sobre la aceptación de certificados de cliente en Kestrel, IIS y Azure, consulte 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:

  • Microsoft Entra ID
  • 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 agregando 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

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.

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;
}

Establecimiento del token de portador con CallCredentials

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.

Ventajas de usar CallCredentials:

  • La autenticación se configura de forma centralizada en el canal. No es necesario proporcionar el token manualmente a la llamada de gRPC.
  • La devolución de llamada de CallCredentials.FromInterceptor es asincrónica. Las credenciales de llamada pueden capturar un token de credencial de un sistema externo si es necesario. Los métodos asincrónicos de la devolución de llamada deben usar CancellationToken en AuthInterceptorContext.

Nota

CallCredentials solo se aplican si el canal está protegido con TLS. El envío de encabezados de autenticación a través de una conexión no segura tiene implicaciones de seguridad y no debe realizarse en entornos de producción. Una aplicación puede configurar un canal para omitir este comportamiento y usar siempre CallCredentials estableciendo UnsafeUseInsecureChannelCallCredentials en un canal.

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

private static GrpcChannel CreateAuthenticatedChannel(ITokenProvder tokenProvider)
{
    var credentials = CallCredentials.FromInterceptor(async (context, metadata) =>
    {
        var token = await tokenProvider.GetTokenAsync(context.CancellationToken);
        metadata.Add("Authorization", $"Bearer {token}");
    });

    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 AddCallCredentials. Este método está disponible en Grpc.Net.ClientFactory, versión 2.46.0 o posterior.

El delegado pasado a AddCallCredentials se ejecuta para cada llamada de gRPC:

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

La inserción de dependencias (DI) se puede combinar con AddCallCredentials. Una sobrecarga pasa IServiceProvider al delegado, que se puede usar para obtener un servicio construido mediante inserción de dependencias usando servicios con ámbito y transitorios.

Imagine una aplicación que tiene:

  • Un ITokenProvider definido por el usuario para obtener un token de portador. ITokenProvider está registrado en la inserción de dependencias con una duración con ámbito.
  • El generador de clientes gRPC está configurado para crear clientes que se insertan en servicios gRPC y controladores de API web.
  • Las llamadas a gRPC deben usar ITokenProvider para obtener un token de portador.
public interface ITokenProvider
{
    Task<string> GetTokenAsync(CancellationToken cancellationToken);
}

public class AppTokenProvider : ITokenProvider
{
    private string _token;

    public async Task<string> GetTokenAsync(CancellationToken cancellationToken)
    {
        if (_token == null)
        {
            // App code to resolve the token here.
        }

        return _token;
    }
}
services.AddScoped<ITokenProvider, AppTokenProvider>();

services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
        o.Address = new Uri("https://localhost:5001");
    })
    .AddCallCredentials(async (context, metadata, serviceProvider) =>
    {
        var provider = serviceProvider.GetRequiredService<ITokenProvider>();
        var token = await provider.GetTokenAsync(context.CancellationToken);
        metadata.Add("Authorization", $"Bearer {token}");
    }));

El código anterior:

  • Define ITokenProvider y AppTokenProvider. Estos tipos controlan la resolución del token de autenticación para las llamadas de gRPC.
  • Registra el tipo AppTokenProvider con DI en una duración con ámbito. AppTokenProvider almacena en caché el token de forma que solo se requiera la primera llamada en el ámbito para su cálculo.
  • Registra el tipo GreeterClient en el generador de clientes.
  • Configura AddCallCredentials para este cliente. El delegado se ejecuta cada vez que se realiza una llamada y agrega el token devuelto por ITokenProvider a los metadatos.

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 información sobre la aceptación de certificados de cliente en Kestrel, IIS y Azure, consulte 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:

  • Microsoft Entra ID
  • 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 agregando 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