Autenticação e autorização no gRPC para ASP.NET Core

Por James Newton-King

Exibir ou fazer download do código de exemplo(como fazer download)

Autenticar usuários chamando um serviço do gRPC

O gRPC pode ser usado com autenticação do ASP.NET Core para associar um usuário a cada chamada.

Veja a seguir um exemplo do Program.cs, o qual usa a autenticação do gRPC e do ASP.NET Core:

app.UseRouting();

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

app.MapGrpcService<GreeterService>();

Observação

A ordem na qual você registra o middleware de autenticação do ASP.NET Core é importante. Sempre chame UseAuthentication e UseAuthorization depois de UseRouting e antes de UseEndpoints.

O mecanismo de autenticação que seu aplicativo usa durante uma chamada precisa ser configurado. A configuração de autenticação é adicionada no Program.cs e será diferente dependendo do mecanismo de autenticação que seu aplicativo usa.

Depois que a autenticação tiver sido configurada, o usuário poderá ser acessado em métodos de serviço do gRPC por meio do ServerCallContext.

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

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

Autenticação de token de portador

O cliente pode fornecer um token de acesso para autenticação. O servidor valida o token e o utiliza para identificar o usuário.

No servidor, a autenticação do token de portador é configurada usando o middleware do Portador JWT.

No cliente gRPC do .NET, o token pode ser enviado com chamadas usando a coleção Metadata. As entradas na coleção Metadata são enviadas com uma chamada do gRPC como cabeçalhos 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;
}

Definir o token de portador com CallCredentials

A configuração ChannelCredentials em um canal é uma maneira alternativa de enviar o token para o serviço com chamadas do gRPC. Um ChannelCredentials pode incluir CallCredentials, que fornece uma maneira de definir automaticamente Metadata.

Benefícios do uso de CallCredentials:

  • A autenticação é configurada centralmente no canal. O token não precisa ser fornecido manualmente para a chamada gRPC.
  • O retorno de chamada CallCredentials.FromInterceptor é assíncrono. As credenciais de chamada podem buscar um token de credencial de um sistema externo, se necessário. Os métodos assíncronos dentro do retorno de chamada devem usar o CancellationToken em AuthInterceptorContext.

Observação

CallCredentials só serão aplicados se o canal for protegido com TLS. O envio de cabeçalhos de autenticação por uma conexão insegura tem implicações de segurança e não deve ser feito em ambientes de produção. Um aplicativo pode configurar um canal para ignorar esse comportamento e sempre usar CallCredentials definindo UnsafeUseInsecureChannelCallCredentials em um canal.

A credencial no exemplo a seguir configura o canal para enviar o token a cada chamada do 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 com fábrica de clientes do gRPC

A fábrica de clientes do gRPC pode criar clientes que enviam um token de portador usando AddCallCredentials. Esse método está disponível no Grpc.Net.ClientFactory versão 2.46.0 ou posterior.

O representante aprovado para AddCallCredentials é executado para cada chamada do 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;
    });

A injeção de dependência (ID) pode ser combinada com AddCallCredentials. Uma sobrecarga aprova o IServiceProvider para o representante, que pode ser usado para obter um serviço construído a partir da ID usando serviços com escopo e transitórios.

Considere um aplicativo que tenha:

  • Um ITokenProvider definido pelo usuário para obter um token de portador. ITokenProvider é registrado em ID com um tempo de vida com escopo.
  • A fábrica de cliente do gRPC é configurada para criar clientes injetados em serviços do gRPC e controladores de API Web.
  • As chamadas do gRPC devem usar o ITokenProvider para obter um 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}");
    }));

O código anterior:

  • Define ITokenProvider e AppTokenProvider. Esses tipos lidam com a resolução do token de autenticação para chamadas do gRPC.
  • Registra o tipo AppTokenProvider com ID em um tempo de vida com escopo. AppTokenProvider armazena em cache o token para que apenas a primeira chamada no escopo seja necessária para calculá-lo.
  • Registra o tipo GreeterClient com fábrica de cliente.
  • Configura AddCallCredentials para esse cliente. O representante é executado sempre que uma chamada é feita e adiciona o token retornado pelo ITokenProvider para os metadados.

Autenticação de certificado do cliente

Como alternativa, um cliente pode fornecer um certificado de cliente para autenticação. A autenticação de certificado ocorre no nível do TLS, muito antes de chegar ao ASP.NET Core. Quando a solicitação entra no ASP.NET Core, o pacote de autenticação de certificado do cliente permite que você resolva o certificado para um ClaimsPrincipal.

Observação

Configure o servidor para aceitar certificados de cliente. Para obter informações sobre como aceitar certificados de cliente no Kestrel, no IIS e no Azure, consulte Configurar a autenticação de certificado no ASP.NET Core.

No cliente gRPC do .NET, o certificado do cliente é adicionado ao HttpClientHandler que é usado para criar o 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);
}

Outros mecanismos de autenticação

Muitos mecanismos de autenticação com suporte do ASP.NET Core funcionam com gRPC:

  • Microsoft Entra ID
  • Certificado do Cliente
  • IdentityServidor
  • JWT Token
  • OAuth 2.0
  • OpenID Connect
  • O certificado do provedor de identidade do Web Services Federation

Para obter mais informações sobre como configurar a autenticação no servidor, consulte autenticação do ASP.NET Core.

Configurar o cliente gRPC para usar a autenticação dependerá do mecanismo de autenticação que você está usando. Os exemplos anteriores de token de portador e certificado do cliente mostram algumas maneiras pelas quais o cliente gRPC pode ser configurado para enviar metadados de autenticação com chamadas gRPC:

  • Clientes gRPC fortemente tipados usam HttpClient internamente. A autenticação pode ser configurada no HttpClientHandlerou adicionando instâncias personalizadas HttpMessageHandler ao HttpClient.
  • Cada chamada do gRPC tem um argumento opcional CallOptions. Cabeçalhos personalizados podem ser enviados usando a coleção de cabeçalhos da opção.

Observação

A Autenticação do Windows (NTLM/Kerberos/Negotiate) não pode ser usada com gRPC. GRPC requer HTTP/2 e HTTP/2 não dá suporte à Autenticação do Windows.

Autorizar usuários a acessar serviços e métodos de serviço

Por padrão, todos os métodos em um serviço podem ser chamados por usuários não autenticados. Para exigir autenticação, aplique o atributo [Authorize] ao serviço:

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

Você pode usar os argumentos do construtor e as propriedades do atributo [Authorize] para restringir o acesso somente a usuários que correspondem a políticas de autorização específicas. Por exemplo, se você tiver uma política de autorização personalizada chamada MyAuthorizationPolicy, verifique se somente os usuários correspondentes a essa política poderão acessar o serviço usando o seguinte código:

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

Métodos de serviço individuais também podem ter o atributo [Authorize] aplicado. Se o usuário atual não corresponder às políticas aplicadas a ambos oa métodos e à classe, um erro será retornado ao chamador:

[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 adicionais

Exibir ou fazer download do código de exemplo(como fazer download)

Autenticar usuários chamando um serviço do gRPC

O gRPC pode ser usado com autenticação do ASP.NET Core para associar um usuário a cada chamada.

Veja a seguir um exemplo do Startup.Configure, o qual usa a autenticação do gRPC e do ASP.NET Core:

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

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

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

Observação

A ordem na qual você registra o middleware de autenticação do ASP.NET Core é importante. Sempre chame UseAuthentication e UseAuthorization depois de UseRouting e antes de UseEndpoints.

O mecanismo de autenticação que seu aplicativo usa durante uma chamada precisa ser configurado. A configuração de autenticação é adicionada no Startup.ConfigureServices e será diferente dependendo do mecanismo de autenticação que seu aplicativo usa.

Depois que a autenticação tiver sido configurada, o usuário poderá ser acessado em métodos de serviço do gRPC por meio do ServerCallContext.

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

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

Autenticação de token de portador

O cliente pode fornecer um token de acesso para autenticação. O servidor valida o token e o utiliza para identificar o usuário.

No servidor, a autenticação do token de portador é configurada usando o middleware do Portador JWT.

No cliente gRPC do .NET, o token pode ser enviado com chamadas usando a coleção Metadata. As entradas na coleção Metadata são enviadas com uma chamada do gRPC como cabeçalhos 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;
}

Definir o token de portador com CallCredentials

A configuração ChannelCredentials em um canal é uma maneira alternativa de enviar o token para o serviço com chamadas do gRPC. Um ChannelCredentials pode incluir CallCredentials, que fornece uma maneira de definir automaticamente Metadata.

Benefícios do uso de CallCredentials:

  • A autenticação é configurada centralmente no canal. O token não precisa ser fornecido manualmente para a chamada gRPC.
  • O retorno de chamada CallCredentials.FromInterceptor é assíncrono. As credenciais de chamada podem buscar um token de credencial de um sistema externo, se necessário. Os métodos assíncronos dentro do retorno de chamada devem usar o CancellationToken em AuthInterceptorContext.

Observação

CallCredentials só serão aplicados se o canal for protegido com TLS. O envio de cabeçalhos de autenticação por uma conexão insegura tem implicações de segurança e não deve ser feito em ambientes de produção. Um aplicativo pode configurar um canal para ignorar esse comportamento e sempre usar CallCredentials definindo UnsafeUseInsecureChannelCallCredentials em um canal.

A credencial no exemplo a seguir configura o canal para enviar o token a cada chamada do 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 com fábrica de clientes do gRPC

A fábrica de clientes do gRPC pode criar clientes que enviam um token de portador usando AddCallCredentials. Esse método está disponível no Grpc.Net.ClientFactory versão 2.46.0 ou posterior.

O representante aprovado para AddCallCredentials é executado para cada chamada do 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;
    });

A injeção de dependência (ID) pode ser combinada com AddCallCredentials. Uma sobrecarga aprova o IServiceProvider para o representante, que pode ser usado para obter um serviço construído a partir da ID usando serviços com escopo e transitórios.

Considere um aplicativo que tenha:

  • Um ITokenProvider definido pelo usuário para obter um token de portador. ITokenProvider é registrado em ID com um tempo de vida com escopo.
  • A fábrica de cliente do gRPC é configurada para criar clientes injetados em serviços do gRPC e controladores de API Web.
  • As chamadas do gRPC devem usar o ITokenProvider para obter um 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}");
    }));

O código anterior:

  • Define ITokenProvider e AppTokenProvider. Esses tipos lidam com a resolução do token de autenticação para chamadas do gRPC.
  • Registra o tipo AppTokenProvider com ID em um tempo de vida com escopo. AppTokenProvider armazena em cache o token para que apenas a primeira chamada no escopo seja necessária para calculá-lo.
  • Registra o tipo GreeterClient com fábrica de cliente.
  • Configura AddCallCredentials para esse cliente. O representante é executado sempre que uma chamada é feita e adiciona o token retornado pelo ITokenProvider para os metadados.

Autenticação de certificado do cliente

Como alternativa, um cliente pode fornecer um certificado de cliente para autenticação. A autenticação de certificado ocorre no nível do TLS, muito antes de chegar ao ASP.NET Core. Quando a solicitação entra no ASP.NET Core, o pacote de autenticação de certificado do cliente permite que você resolva o certificado para um ClaimsPrincipal.

Observação

Configure o servidor para aceitar certificados de cliente. Para obter informações sobre como aceitar certificados de cliente no Kestrel, no IIS e no Azure, consulte Configurar a autenticação de certificado no ASP.NET Core.

No cliente gRPC do .NET, o certificado do cliente é adicionado ao HttpClientHandler que é usado para criar o 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);
}

Outros mecanismos de autenticação

Muitos mecanismos de autenticação com suporte do ASP.NET Core funcionam com gRPC:

  • Microsoft Entra ID
  • Certificado do Cliente
  • IdentityServidor
  • JWT Token
  • OAuth 2.0
  • OpenID Connect
  • O certificado do provedor de identidade do Web Services Federation

Para obter mais informações sobre como configurar a autenticação no servidor, consulte autenticação do ASP.NET Core.

Configurar o cliente gRPC para usar a autenticação dependerá do mecanismo de autenticação que você está usando. Os exemplos anteriores de token de portador e certificado do cliente mostram algumas maneiras pelas quais o cliente gRPC pode ser configurado para enviar metadados de autenticação com chamadas gRPC:

  • Clientes gRPC fortemente tipados usam HttpClient internamente. A autenticação pode ser configurada no HttpClientHandlerou adicionando instâncias personalizadas HttpMessageHandler ao HttpClient.
  • Cada chamada do gRPC tem um argumento opcional CallOptions. Cabeçalhos personalizados podem ser enviados usando a coleção de cabeçalhos da opção.

Observação

A Autenticação do Windows (NTLM/Kerberos/Negotiate) não pode ser usada com gRPC. GRPC requer HTTP/2 e HTTP/2 não dá suporte à Autenticação do Windows.

Autorizar usuários a acessar serviços e métodos de serviço

Por padrão, todos os métodos em um serviço podem ser chamados por usuários não autenticados. Para exigir autenticação, aplique o atributo [Authorize] ao serviço:

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

Você pode usar os argumentos do construtor e as propriedades do atributo [Authorize] para restringir o acesso somente a usuários que correspondem a políticas de autorização específicas. Por exemplo, se você tiver uma política de autorização personalizada chamada MyAuthorizationPolicy, verifique se somente os usuários correspondentes a essa política poderão acessar o serviço usando o seguinte código:

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

Métodos de serviço individuais também podem ter o atributo [Authorize] aplicado. Se o usuário atual não corresponder às políticas aplicadas a ambos oa métodos e à classe, um erro será retornado ao chamador:

[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 adicionais