Authentication .NET Core gRpc service with JWT
--
In this article I’ll bring together traits about authentication in gRpc service with JWT. I also assume that you already have experience with JWT and HTTP headers in .NET Core WebAPI. The reason for this article to be written is that the majority of examples related to authentication in gRpc is written using console applications which is too far from reality which developers need. In real application I don’t want to create a channel every time I need it. I also don’t want to care about sending the token and user information with each request. Instead of this, I want to have an infrastructure layer which will care about it and sends required information implicitly. If you are interested in this, then read further. I’m using .NET Core 3.1 in this article.
Sample Solution
Before going into details, I want to describe the test solution which will be used in examples. The solution consists of two projects: a client application and gRpc API service. Users can login in the application and browse some data if he is authorized to see it. The web application doesn’t persist user data and for user authentication relies on API. A web application needs to have a JWT token to work with API. But this JWT token does nothing about user authentication in the application. The web application uses cookie authentication on its side to retain user identity. In order to let the API know which user is making a request the information is accompanied with a request along with JWT token. The figure below illustrates what I just said.
Here I should make a note that when I was doing this sample, I didn’t have the purpose to implement the most correct authentication flow for API. You can check OpenID Connect specification to dig into the correct one. Although I think sometimes the most correct solution is not what might just solve the problem, saving time and money.
Enabling JWT Authentication in gRpc service
The configuration of gRpc service is not different from a regular configuration that .NET Core API requires. Also, it doesn’t vary depending on the protocol which we use, HTTP or HTTPS. In a few words, you need to add standard authentication and authorization services and middleware in Startup.cs
file. The position of middleware is important. You need to add it exactly between routing and endpoints (some code is skipped):
The position of service registering is not important, but you need to configure validation of JWT token. It’s possible to define it right here, but I’d recommend extracting routines in a separate class. So, the code may looks as follow:
The class JwtTokenValidator
is the one where you’ll define the validation logic. You will define TokenValidationParameters
which will do all work for validation of JWT. And also, you can add an additional layer of security here. You may want to add it because JWT is a well-known format which means that if you have JWT you can go to jwt.io and see some information. In my case I prefer to add additional encryption to JWT which makes it much harder to decrypt. This is how the validator may look like:
And that’s what you need on the API side. The story about client configuration is a bit longer and a bit different depending on HTTP or HTTPS you want to use.
Sending HTTP headers with every request to gRpc service
You may know already this sample code from official documentation which you cannot use anywhere, except in dumb console application. You can find it in such tutorials like this.
var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);var response = await client.SayHelloAsync(
new HelloRequest { Name = "World" });Console.WriteLine(response.Message);
In order to use this in real application we need to have centralized configuration and DI, which is barely covered. Here is what you need to do. First, we need to add required NuGet packages to our project.
dotnet add package Grpc.Net.Client
dotnet add package Google.Protobuf
dotnet add package Grpc.Tools
dotnet add package Grpc.Net.ClientFactory
The package Grpc.Tools
will help to build proto files during project build, and Grpc.Net.ClientFactory
will help to configure DI.
Working with gRpc, if you need to inject your routines somewhere in the middle of request-response flow, you need to use interceptor classes. Such classes are inherited from Interceptor
base class which is the part of gRpc.Core
. If you need to access HttpContext.User.Identity
inside your services, you can inject the interface IHttpContextAccessor
in your service. It requires additional registration in services. You need to add the following to your Startup.cs
.
The class AuthHeadersInterceptor
is our own class derived from Interceptor
class. It uses IHttpContextAccessor
and the registration .AddHttpContextAccessor()
allows it to do this.
HTTP Traits
You may note the following configuration:
httpClientBuilder.ConfigureChannel(o => o.Credentials = ChannelCredentials.Insecure);
It is required to work via HTTP, but it’s not enough. You also need to exclude that line from Configure()
method.
app.UseHttpsRedirection();
And you need to add this configuration before any gRpc channel creation. It should be executed only once during the application startup. So, I added to almost the same position as the line mentioned above. And it should be called only for HTTP.
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
HTTPS Traits
There are certain pitfalls of working with SSL on Windows and Linux. It may happen that you’re developing on a Windows machine and deploying to Docker/Kubernetes using Linux-based images. In such a case the configuration is not straightforward. I’ll describe that configuration in another article, and here I’ll shed the light on configuration inside the code.
We need to change the configuration of the gRpc channel to use SSL credentials. If you deploy in Docker using a Linux-based image you also may need to configure HttpClient
to allow untrusted certificates. HttpClient
is created for every channel.
Adding HTTP Headers
The headers are added inside interceptor class. gRpc uses the concept of metadata, which is sent along with requests as headers. The interceptor class should add metadata for call context.
For the scenario when you just call gRpc service, you need to override only AsyncUnaryCall
methods. JWT token can be inserted via configuration.
And that’s it. Later I’ll add a code reference to a simple sample of the described use case. If you have any additional questions, please, write to me. I’ll try to answer.