Authenticate gRPC service in ASP.NET Core

ASP.NET Core supports creation of RPC style services using gRPC. Once created
you might also want to secure them by authenticating and authorizing the service
calls. To that end this article discusses a possible approach to implementing
authentication in gRPC services.
To work through the example discussed in this article you need to be familiar
with gRPC in ASP.NET Core and IdentityServer. I am not going to cover basics of
these technologies here. If you are new to these topics I suggest you read my
article series about gRPC (Part
1, Part 2,
Part 3,
Part 4) and
IdentityServer (Part 1,
Part 2,
Part 3,
Part 4,
Part 5,
Part 6). I am going to
use the same Employees gRPC service that we developed in the mentioned article
series. There are three projects involved - gRPC service project, gRPC client
project, and IdentityServer server project.
The Solution explorer containing these projects is shown below:

Before going any further arrange your Visual Studio solution as shown above.
Read the articles mentioned above and you will have everything required to write
the code discussed in this article.
First of all we will make some modifications to the IdentityServer server
project. Especially we need to setup an ApiResource for the gRPC service and
also make some changes to the MVC app client configuration.
So, open ServerConfiguration class from the IdentityServer server project and
go to the ApiResources and ApiScopes properties.
public static List<ApiScope> ApiScopes
{
get
{
List<ApiScope> apiScopes = new List<ApiScope>();
apiScopes.Add(new ApiScope("employeesGrpcService",
"Employees gRPC Service"));
return apiScopes;
}
}
public static List<ApiResource> ApiResources
{
get
{
ApiResource apiResource1 = new ApiResource
("employeesGrpcServiceResource", "Employees gRPC Service")
{
Scopes = { "employeesGrpcService" },
UserClaims = { "role",
"given_name",
"family_name"
}
};
List<ApiResource> apiResources =
new List<ApiResource>();
apiResources.Add(apiResource1);
return apiResources;
}
}
As you can see we created an ApiScope and ApiResource for our gRPC service.
Then we need to modify the client definition for the MVC web app as shown
below:
public static List<Client> Clients
{
get
{
Client client = new Client
{
ClientName = "Client 2",
ClientId = "client2",
AllowedGrantTypes = GrantTypes.Hybrid,
RedirectUris = new List<string> {
"https://localhost:5011/signin-oidc"
},
RequirePkce = false,
AllowedScopes = {
IdentityServerConstants.StandardScopes.OpenId,
IdentityServerConstants.StandardScopes.Profile,
"employeesGrpcService",
"roles"
},
ClientSecrets = {
new Secret("client_secret_code".Sha512())
},
PostLogoutRedirectUris = new List<string> {
"https://localhost:5011/signout-callback-oidc"
},
RequireConsent = true
};
List<Client> clients = new List<Client>();
clients.Add(client);
return clients;
}
}
This completes the modifications required for the IdentityServer server
project. Now, let's move on to the gRPC service project. This is the project
that contains our Employees gRPC service.
Open the Startup class of the gRPC service project and enable JWT based
authentication as shown below:
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc(options =>
{
options.EnableDetailedErrors = true;
});
services.AddDbContext<AppDbContext>
(options => options.UseSqlServer
(this.config.GetConnectionString("AppDb")));
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap
.Clear();
services.AddAuthorization();
services.AddAuthentication("Bearer")
.AddJwtBearer(o =>
{
o.Authority = "https://localhost:5001";
o.RequireHttpsMetadata = false;
o.Audience = "employeesGrpcServiceResource";
o.TokenValidationParameters =
new TokenValidationParameters
{
RoleClaimType = "role",
};
});
}
Notice the AddJwtBearer() method. Note that the Authority property must point
to the URL of the IdentityServer server application.
Go to the Configure() method and add this code :
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(...)
Make sure to place the UserAuthentication() and UseAuthorization() calls in
between UseRouting() and UseEndpoints().
Now open the EmployeeCRUDService class from the Services folder and decorate
it with [Authorize] attribute like this :
[Authorize(Roles = "Admin")]
public class EmployeeCRUDService :
EmployeeCRUD.EmployeeCRUDBase
{
...
...
}
We set the Roles property of [Authorize] to Admin. Recollect that
IdentityServer's TestUser user1 has role configured to Admin (see
ServerConfiguration class for more details).
The EmployeeCRUDService is now protected using JWT authentication. In order
to invoke any of its methods you need to supply a valid JWT with the call. This
JWT will be provided by the IdentityServer server application.
Now let's move on to the third part of the solution - the MVC client
application.
Open the gRPC MVC client web application and go to its Startup class. We have
protected the gRPC service using JWT. We would also like to protect the client
app using OIDC. Add the following code to the ConfigureServices() method.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
string connStr = this.config.
GetConnectionString("AppDb");
services.AddAuthentication(o => {
o.DefaultScheme = "Cookies";
o.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies", o =>
{
o.AccessDeniedPath = "/Account/AccessDenied";
})
.AddOpenIdConnect("oidc", o =>
{
o.ClientId = "client2";
o.ClientSecret = "client_secret_code";
o.SignInScheme = "Cookies";
o.Authority = "https://localhost:5001";
o.RequireHttpsMetadata = false;
o.ResponseType = "code id_token";
o.SaveTokens = true;
o.GetClaimsFromUserInfoEndpoint = true;
o.Scope.Add("employeesGrpcService");
o.Scope.Add("roles");
o.ClaimActions.MapUniqueJsonKey("role", "role");
o.TokenValidationParameters = new
TokenValidationParameters
{
RoleClaimType = "role"
};
});
}
If you read my IdentityServer article series, this configuration should look
familiar to you. Make sure to use the correct ClientId and ClientSecret as per
your ServerConfiguration values. Also make sure to have scope for
employeesGrpcService.
The MVC gRPC client application contains the HomeController class. Decorate
it with [Authorize] as shown below:
[Authorize(Roles = "Admin")]
public class HomeController : Controller
{
...
}
Let's quickly test the configuration so far.
Run the IdentityServer server project, gRPC service project, and gRPC client
project in the same sequence. If all goes well you will be shown a login page
like this:

If you supply user1 credentials you will be able to log into the gRPC MVC
client app but the actual call to the gRPC service will fail because we aren't
passing a valid JWT to the gRPC service yet.
There are two ways to pass a JWT to the gRPC service. You can pass a JWT with
each and every call made to the service OR you can set a JWT at the time of
creating the gRPC channel.
Let's see the first possibility.
public IActionResult Index()
{
var accessToken = HttpContext.GetTokenAsync
(OpenIdConnectParameterNames.AccessToken).Result;
var channel = GrpcChannel.ForAddress
("https://localhost:5021");
var client = new EmployeeCRUD.
EmployeeCRUDClient(channel);
var headerMetadata = new Metadata();
headerMetadata.Add("Authorization",
$"Bearer {accessToken}");
CallOptions callOptions = new CallOptions
(headerMetadata);
Empty response1 = client.Insert(new Employee()
{
FirstName = "Tom",
LastName = "Jerry"
}, callOptions);
Employees employees = client.SelectAll(new Empty(),
callOptions);
return View(employees);
}
Notice the code shown in bold letters. First, we get the JWT token by calling
GetTokenAsync() on the HttpContext. We then create a Metadata object that holds
the Authorization header information. The Authorization header value is set to
Bearer followed by the JWT token. Then we create a CallOptions object based on
the Metadata.
Then we call Insert() method to insert a new Employee. Notice the second
parameter of Insert(). It's the CallOptions object we just created. To verify
whether the new employee got added or not we call SelectAll() method (again we
pass the CallOptions object as its second parameter).
The following figure shows a list of employees rendered on the Index view.

As you can see, you need to pass CallOptions object with each and every call
to the gRPC service. You can also set the JWT while creating the gRPC channel.
That way you need not explicitly pass CallOptions with each call. Let's see how
that can be done. Take a look at the following modified Index() action.
public IActionResult Index()
{
var accessToken = HttpContext.GetTokenAsync
(OpenIdConnectParameterNames.AccessToken).Result;
var callCredentials = CallCredentials.
FromInterceptor((context, metadata) =>
{
metadata.Add("Authorization",
$"Bearer {accessToken}");
return Task.CompletedTask;
});
var channelOptions = new GrpcChannelOptions();
channelOptions.Credentials =
ChannelCredentials.Create(new SslCredentials(),
callCredentials);
var channel = GrpcChannel.ForAddress
("https://localhost:5021", channelOptions);
var client = new EmployeeCRUD.EmployeeCRUDlient(channel);
Empty response1 = client.Insert(new Employee()
{
FirstName = "Tom",
LastName = "Jerry"
});
Employees employees = client.SelectAll(new Empty());
return View(employees);
}
This time we created
CallCredentials object by adding Authorization header. We then created
ChannelOptions object based on the CallCredentials just created. Finally we pass
the ChannelCredentials object as the second parameter of ForAddress() method.
Make sure to remove the CallOptions parameter from Insert() and SelectAll()
methods since we are now passing the JWT through CallCredentials and
ChannelCredentials objects.
To know more about authentication and authorization in gRPC go
here.
That's it for now! Keep coding!!