Authentification et autorisation dans gRPC pour ASP.NET Core

Par James Newton-King

Afficher ou télécharger l’exemple de code(procédure de téléchargement)

Authentifier les utilisateurs appelant un service gRPC

gRPC peut être utilisé avec l’authentification ASP.NET Core pour associer un utilisateur à chaque appel.

Voici un exemple de Program.cs qui utilise gRPC et l’authentification ASP.NET Core :

app.UseRouting();

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

app.MapGrpcService<GreeterService>();

Remarque

L’ordre dans lequel vous inscrivez l’intergiciel d’authentification ASP.NET Core est important. Appelez toujours UseAuthentication et UseAuthorization après UseRouting et avant UseEndpoints.

Le mécanisme d’authentification utilisé par votre application lors d’un appel doit être configuré. La configuration de l’authentification est ajoutée dans Program.cs et sera différente en fonction du mécanisme d’authentification utilisé par votre application.

Une fois l’authentification configurée, l’utilisateur est accessible dans les méthodes d’un service gRPC via le ServerCallContext.

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

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

Authentification des jetons du porteur

Le client peut fournir un jeton d’accès pour l’authentification. Le serveur valide le jeton et l’utilise pour identifier l’utilisateur.

Sur le serveur, l’authentification par jeton du porteur est configurée à l’aide de l’intergiciel JWT Bearer.

Dans le client .NET gRPC, le jeton peut être envoyé avec des appels à l’aide de la collection Metadata. Les entrées de la collection Metadata sont envoyées avec un appel gRPC en tant qu’en-têtes 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;
}

Définir le jeton du porteur avec CallCredentials

La configuration ChannelCredentials sur un canal est une autre façon d’envoyer le jeton au service avec des appels gRPC. Un ChannelCredentials peut inclure CallCredentials, qui fournit un moyen de définir Metadata automatiquement .

Avantages de l’utilisation de CallCredentials :

  • L’authentification est configurée de manière centralisée sur le canal. Le jeton n’a pas besoin d’être fourni manuellement à l’appel gRPC.
  • Le rappel CallCredentials.FromInterceptor est asynchrone. Les informations d’identification de l’appel peuvent extraire un jeton d’informations d’identification depuis un système externe si nécessaire. Les méthodes asynchrones dans le rappel doivent utiliser le CancellationToken sur AuthInterceptorContext.

Remarque

CallCredentials sont appliqués uniquement si le canal est sécurisé avec TLS. L’envoi d’en-têtes d’authentification sur une connexion non sécurisée a des implications sur la sécurité et ne doit pas être effectué dans les environnements de production. Une application peut configurer un canal pour ignorer ce comportement et toujours utiliser CallCredentials en définissant UnsafeUseInsecureChannelCallCredentials sur un canal.

Dans l’exemple suivant, les informations d’identification configurent le canal pour envoyer le jeton à chaque appel 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;
}

Jeton du porteur avec fabrique de clients gRPC

La fabrique de clients gRPC peut créer des clients qui envoient un jeton de porteur à l’aide de AddCallCredentials. Cette méthode est disponible dans Grpc.Net.ClientFactory version 2.46.0 ou ultérieure.

Le délégué transmis à AddCallCredentials est exécuté pour chaque appel 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;
    });

L’injection de dépendances (DI) peut être combinée avec AddCallCredentials. Une surcharge transmet IServiceProvider au délégué, qui peut être utilisée pour obtenir un service construit à partir d’une DI à l’aide de services étendus et temporaires.

Prenons l’exemple d’une application qui a :

  • Un ITokenProvider défini par l’utilisateur pour obtenir un jeton du porteur. ITokenProvider est inscrit dans une DI avec une durée de vie étendue.
  • La fabrique de clients gRPC est configurée pour créer des clients injectés dans des services gRPC et des contrôleurs d’API web.
  • Les appels gRPC doivent utiliser ITokenProvider pour obtenir un jeton du porteur.
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}");
    }));

Le code précédent :

  • Définit ITokenProvider et AppTokenProvider. Ces types gèrent la résolution du jeton d’authentification pour les appels gRPC.
  • Inscrit le type AppTokenProvider avec DI dans une durée de vie étendue. AppTokenProvider met en cache le jeton afin que seul le premier appel dans l’étendue soit requis pour le calculer.
  • Inscrit le type GreeterClient auprès de la fabrique de clients.
  • Configure un AddCallCredentials pour ce client. Le délégué est exécuté chaque fois qu’un appel est effectué et ajoute le jeton retourné par ITokenProvider aux métadonnées.

Authentification par certificat client

Un client peut également fournir un certificat client pour l’authentification. L’authentification par certificat se produit au niveau TLS, bien avant qu’elle n’arrive à ASP.NET Core. Lorsque la requête arrive à ASP.NET Core, le package d’authentification de certificat client vous permet de résoudre le certificat en ClaimsPrincipal.

Remarque

Configurez le serveur pour accepter des certificats clients. Pour plus d’informations sur l’acceptation des certificats clients dans Kestrel, IIS et Azure, consultez Configurer l’authentification par certificat dans ASP.NET Core.

Dans le client .NET gRPC, le certificat client est ajouté à HttpClientHandler qui est ensuite utilisé pour créer le client 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);
}

Autres mécanismes d’authentification

De nombreux mécanismes d’authentification pris en charge par ASP.NET Core fonctionnent avec gRPC :

  • Microsoft Entra ID
  • Certificat client
  • IdentityServeur
  • Jeton JWT
  • OAuth 2.0
  • OpenID Connect
  • WS-Federation

Pour plus d’informations sur la configuration de l’authentification sur le serveur, consultez Authentification ASP.NET Core.

La configuration du client gRPC pour utiliser l’authentification dépend du mécanisme d’authentification que vous utilisez. Les exemples précédents de jeton du porteur et de certificat client montrent deux façons de configurer le client gRPC pour envoyer des métadonnées d’authentification avec des appels gRPC :

  • Les clients gRPC fortement typés utilisent HttpClient en interne. L’authentification peut être configurée sur HttpClientHandler ou en ajoutant des instances HttpMessageHandler personnalisées à HttpClient.
  • Chaque appel gRPC a un argument CallOptions facultatif. Les en-têtes personnalisés peuvent être envoyés à l’aide de la collection d’en-têtes de l’option.

Remarque

L’authentification Windows (NTLM/Kerberos/Negotiate) ne peut pas être utilisée avec gRPC. gRPC nécessite HTTP/2, et HTTP/2 ne prend pas en charge l’authentification Windows.

Autoriser les utilisateurs à accéder aux services et aux méthodes de service

Par défaut, toutes les méthodes d’un service peuvent être appelées par des utilisateurs non authentifiés. Pour exiger l’authentification, appliquez l’attribut [Authorize] au service :

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

Vous pouvez utiliser les arguments du constructeur et les propriétés de l’attribut [Authorize] pour restreindre l’accès aux seuls utilisateurs correspondant à des stratégies d’autorisation spécifiques. Par exemple, si vous avez une stratégie d’autorisation personnalisée appelée MyAuthorizationPolicy, assurez-vous que seuls les utilisateurs correspondant à cette stratégie peuvent accéder au service à l’aide du code suivant :

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

L’attribut [Authorize] peut également être appliqué à des méthodes de service individuelles. Si l’utilisateur actuel ne correspond pas aux stratégies appliquées à la fois à la méthode et à la classe, une erreur est retournée à l’appelant :

[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) ..
    }
}

Ressources supplémentaires

Afficher ou télécharger l’exemple de code(procédure de téléchargement)

Authentifier les utilisateurs appelant un service gRPC

gRPC peut être utilisé avec l’authentification ASP.NET Core pour associer un utilisateur à chaque appel.

Voici un exemple de Startup.Configure qui utilise gRPC et l’authentification ASP.NET Core :

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

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

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

Remarque

L’ordre dans lequel vous inscrivez l’intergiciel d’authentification ASP.NET Core est important. Appelez toujours UseAuthentication et UseAuthorization après UseRouting et avant UseEndpoints.

Le mécanisme d’authentification utilisé par votre application lors d’un appel doit être configuré. La configuration de l’authentification est ajoutée dans Startup.ConfigureServices et sera différente en fonction du mécanisme d’authentification utilisé par votre application.

Une fois l’authentification configurée, l’utilisateur est accessible dans les méthodes d’un service gRPC via le ServerCallContext.

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

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

Authentification des jetons du porteur

Le client peut fournir un jeton d’accès pour l’authentification. Le serveur valide le jeton et l’utilise pour identifier l’utilisateur.

Sur le serveur, l’authentification par jeton du porteur est configurée à l’aide de l’intergiciel JWT Bearer.

Dans le client .NET gRPC, le jeton peut être envoyé avec des appels à l’aide de la collection Metadata. Les entrées de la collection Metadata sont envoyées avec un appel gRPC en tant qu’en-têtes 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;
}

Définir le jeton du porteur avec CallCredentials

La configuration ChannelCredentials sur un canal est une autre façon d’envoyer le jeton au service avec des appels gRPC. Un ChannelCredentials peut inclure CallCredentials, qui fournit un moyen de définir Metadata automatiquement .

Avantages de l’utilisation de CallCredentials :

  • L’authentification est configurée de manière centralisée sur le canal. Le jeton n’a pas besoin d’être fourni manuellement à l’appel gRPC.
  • Le rappel CallCredentials.FromInterceptor est asynchrone. Les informations d’identification de l’appel peuvent extraire un jeton d’informations d’identification depuis un système externe si nécessaire. Les méthodes asynchrones dans le rappel doivent utiliser le CancellationToken sur AuthInterceptorContext.

Remarque

CallCredentials sont appliqués uniquement si le canal est sécurisé avec TLS. L’envoi d’en-têtes d’authentification sur une connexion non sécurisée a des implications sur la sécurité et ne doit pas être effectué dans les environnements de production. Une application peut configurer un canal pour ignorer ce comportement et toujours utiliser CallCredentials en définissant UnsafeUseInsecureChannelCallCredentials sur un canal.

Dans l’exemple suivant, les informations d’identification configurent le canal pour envoyer le jeton à chaque appel 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;
}

Jeton du porteur avec fabrique de clients gRPC

La fabrique de clients gRPC peut créer des clients qui envoient un jeton de porteur à l’aide de AddCallCredentials. Cette méthode est disponible dans Grpc.Net.ClientFactory version 2.46.0 ou ultérieure.

Le délégué transmis à AddCallCredentials est exécuté pour chaque appel 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;
    });

L’injection de dépendances (DI) peut être combinée avec AddCallCredentials. Une surcharge transmet IServiceProvider au délégué, qui peut être utilisée pour obtenir un service construit à partir d’une DI à l’aide de services étendus et temporaires.

Prenons l’exemple d’une application qui a :

  • Un ITokenProvider défini par l’utilisateur pour obtenir un jeton du porteur. ITokenProvider est inscrit dans une DI avec une durée de vie étendue.
  • La fabrique de clients gRPC est configurée pour créer des clients injectés dans des services gRPC et des contrôleurs d’API web.
  • Les appels gRPC doivent utiliser ITokenProvider pour obtenir un jeton du porteur.
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}");
    }));

Le code précédent :

  • Définit ITokenProvider et AppTokenProvider. Ces types gèrent la résolution du jeton d’authentification pour les appels gRPC.
  • Inscrit le type AppTokenProvider avec DI dans une durée de vie étendue. AppTokenProvider met en cache le jeton afin que seul le premier appel dans l’étendue soit requis pour le calculer.
  • Inscrit le type GreeterClient auprès de la fabrique de clients.
  • Configure un AddCallCredentials pour ce client. Le délégué est exécuté chaque fois qu’un appel est effectué et ajoute le jeton retourné par ITokenProvider aux métadonnées.

Authentification par certificat client

Un client peut également fournir un certificat client pour l’authentification. L’authentification par certificat se produit au niveau TLS, bien avant qu’elle n’arrive à ASP.NET Core. Lorsque la requête arrive à ASP.NET Core, le package d’authentification de certificat client vous permet de résoudre le certificat en ClaimsPrincipal.

Remarque

Configurez le serveur pour accepter des certificats clients. Pour plus d’informations sur l’acceptation des certificats clients dans Kestrel, IIS et Azure, consultez Configurer l’authentification par certificat dans ASP.NET Core.

Dans le client .NET gRPC, le certificat client est ajouté à HttpClientHandler qui est ensuite utilisé pour créer le client 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);
}

Autres mécanismes d’authentification

De nombreux mécanismes d’authentification pris en charge par ASP.NET Core fonctionnent avec gRPC :

  • Microsoft Entra ID
  • Certificat client
  • IdentityServeur
  • Jeton JWT
  • OAuth 2.0
  • OpenID Connect
  • WS-Federation

Pour plus d’informations sur la configuration de l’authentification sur le serveur, consultez Authentification ASP.NET Core.

La configuration du client gRPC pour utiliser l’authentification dépend du mécanisme d’authentification que vous utilisez. Les exemples précédents de jeton du porteur et de certificat client montrent deux façons de configurer le client gRPC pour envoyer des métadonnées d’authentification avec des appels gRPC :

  • Les clients gRPC fortement typés utilisent HttpClient en interne. L’authentification peut être configurée sur HttpClientHandler ou en ajoutant des instances HttpMessageHandler personnalisées à HttpClient.
  • Chaque appel gRPC a un argument CallOptions facultatif. Les en-têtes personnalisés peuvent être envoyés à l’aide de la collection d’en-têtes de l’option.

Remarque

L’authentification Windows (NTLM/Kerberos/Negotiate) ne peut pas être utilisée avec gRPC. gRPC nécessite HTTP/2, et HTTP/2 ne prend pas en charge l’authentification Windows.

Autoriser les utilisateurs à accéder aux services et aux méthodes de service

Par défaut, toutes les méthodes d’un service peuvent être appelées par des utilisateurs non authentifiés. Pour exiger l’authentification, appliquez l’attribut [Authorize] au service :

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

Vous pouvez utiliser les arguments du constructeur et les propriétés de l’attribut [Authorize] pour restreindre l’accès aux seuls utilisateurs correspondant à des stratégies d’autorisation spécifiques. Par exemple, si vous avez une stratégie d’autorisation personnalisée appelée MyAuthorizationPolicy, assurez-vous que seuls les utilisateurs correspondant à cette stratégie peuvent accéder au service à l’aide du code suivant :

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

L’attribut [Authorize] peut également être appliqué à des méthodes de service individuelles. Si l’utilisateur actuel ne correspond pas aux stratégies appliquées à la fois à la méthode et à la classe, une erreur est retournée à l’appelant :

[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) ..
    }
}

Ressources supplémentaires