Skip to content

Commit b54a384

Browse files
authored
Add docs for new gRPC call credentials features (#25734)
1 parent c3e9035 commit b54a384

File tree

3 files changed

+102
-64
lines changed

3 files changed

+102
-64
lines changed

aspnetcore/grpc/authn-and-authz.md

Lines changed: 79 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,10 @@ public bool DoAuthenticatedCall(
7373
}
7474
```
7575

76-
Configuring `ChannelCredentials` on a channel is an alternative way to send the token to the service with gRPC calls. A `ChannelCredentials` can include `CallCredentials`, which provide a way to automatically set `Metadata`.
76+
Configuring `ChannelCredentials` on a channel is an alternative way to send the token to the service with gRPC calls. A `ChannelCredentials` can include `CallCredentials`, which provide a way to automatically set `Metadata`. `CallCredentials` is run each time a gRPC call is made, which avoids the need to write code in multiple places to pass the token yourself.
7777

78-
`CallCredentials` is run each time a gRPC call is made, which avoids the need to write code in multiple places to pass the token yourself. Note that `CallCredentials` are only applied if the channel is secured with TLS. `CallCredentials` aren't applied on unsecured non-TLS channels.
78+
> [!NOTE]
79+
> `CallCredentials` are only applied if the channel is secured with TLS. Sending authentication headers over an insecure connection has security implications and shouldn't be done in production environments. An app can configure a channel to ignore this behavior and always use `CallCredentials` by setting `UnsafeUseInsecureChannelCallCredentials` on a channel.
7980
8081
The credential in the following example configures the channel to send the token with every gRPC call:
8182

@@ -91,8 +92,6 @@ private static GrpcChannel CreateAuthenticatedChannel(string address)
9192
return Task.CompletedTask;
9293
});
9394

94-
// SslCredentials is used here because this channel is using TLS.
95-
// CallCredentials can't be used with ChannelCredentials.Insecure on non-TLS channels.
9695
var channel = GrpcChannel.ForAddress(address, new GrpcChannelOptions
9796
{
9897
Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)
@@ -103,70 +102,78 @@ private static GrpcChannel CreateAuthenticatedChannel(string address)
103102

104103
#### Bearer token with gRPC client factory
105104

106-
gRPC client factory can create clients that send a bearer token using `ChannelCredentials`. When configuring a client, assign the `CallCredentials` the client should use with the `ConfigureChannel` method.
105+
gRPC client factory can create clients that send a bearer token using `AddCallCredentials`. This method is available in [Grpc.Net.ClientFactory](https://www.nuget.org/packages/Grpc.Net.ClientFactory) version 2.46.0 or later.
106+
107+
The delegate passed to `AddCallCredentials` is executed for each gRPC call:
107108

108109
```csharp
109110
builder.Services
110111
.AddGrpcClient<Greeter.GreeterClient>(o =>
111112
{
112113
o.Address = new Uri("https://localhost:5001");
113114
})
114-
.ConfigureChannel(o =>
115+
.AddCallCredentials((context, metadata) =>
115116
{
116-
var credentials = CallCredentials.FromInterceptor((context, metadata) =>
117+
if (!string.IsNullOrEmpty(_token))
117118
{
118-
if (!string.IsNullOrEmpty(_token))
119-
{
120-
metadata.Add("Authorization", $"Bearer {_token}");
121-
}
122-
return Task.CompletedTask;
123-
});
124-
125-
o.Credentials = ChannelCredentials.Create(new SslCredentials(), credentials);
119+
metadata.Add("Authorization", $"Bearer {_token}");
120+
}
121+
return Task.CompletedTask;
126122
});
127123
```
128124

129-
A gRPC interceptor can also be used to configure a bearer token. An advantage to using an interceptor is the client factory can be configured to create a new interceptor for each client. This allows an interceptor to be [constructed from DI using scoped and transient services](/dotnet/core/extensions/dependency-injection#service-lifetimes).
125+
Dependency injection (DI) can be combined with `AddCallCredentials`. An overload passes `IServiceProvider` to the delegate, which can be used to get a service [constructed from DI using scoped and transient services](/dotnet/core/extensions/dependency-injection#service-lifetimes).
130126

131127
Consider an app that has:
128+
132129
* A user-defined `ITokenProvider` for getting a bearer token. `ITokenProvider` is registered in DI with a scoped lifetime.
133130
* gRPC client factory is configured to create clients that are injected into gRPC services and Web API controllers.
134131
* gRPC calls should use `ITokenProvider` to get a bearer token.
135132

136133
```csharp
137-
public class AuthInterceptor : Interceptor
134+
public interface ITokenProvider
138135
{
139-
private readonly ITokenProvider _tokenProvider;
140-
141-
public AuthInterceptor(ITokenProvider tokenProvider)
142-
{
143-
_tokenProvider = tokenProvider;
144-
}
145-
146-
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
147-
TRequest request,
148-
ClientInterceptorContext<TRequest, TResponse> context,
149-
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
136+
Task<string> GetTokenAsync();
137+
}
138+
139+
public class AppTokenProvider : ITokenProvider
140+
{
141+
private string _token;
142+
143+
public async Task<string> GetTokenAsync()
150144
{
151-
context.Options.Metadata.Add("Authorization", $"Bearer {_tokenProvider.GetToken()}");
152-
return continuation(request, context);
145+
if (_token == null)
146+
{
147+
// App code to resolve the token here.
148+
}
149+
150+
return _token;
153151
}
154152
}
155153
```
156154

157155
```csharp
156+
builder.Services.AddScoped<ITokenProvider, AppTokenProvider>();
157+
158158
builder.Services
159159
.AddGrpcClient<Greeter.GreeterClient>(o =>
160160
{
161161
o.Address = new Uri("https://localhost:5001");
162162
})
163-
.AddInterceptor<AuthInterceptor>(InterceptorScope.Client);
163+
.AddCallCredentials(async (context, metadata, serviceProvider) =>
164+
{
165+
var provider = serviceProvider.GetRequiredService<ITokenProvider>();
166+
var token = await provider.GetTokenAsync();
167+
metadata.Add("Authorization", $"Bearer {token}");
168+
}));
164169
```
165170

166-
The preceeding code:
167-
* Defines `AuthInterceptor` which is constructed using the user defined `ITokenProvider`.
171+
The preceding code:
172+
173+
* Defines `ITokenProvider` and `AppTokenProvider`. These types handle resolving the authentication token for gRPC calls.
174+
* Registers the `AppTokenProvider` type with DI in a scoped lifetime. `AppTokenProvider` caches the token so that only the first call in the scope is required to calculate it.
168175
* Registers the `GreeterClient` type with client factory.
169-
* Configures the `AuthInterceptor` for this client using `InterceptorScope.Client`. A new interceptor is created for each client instance. When a client is created for a gRPC service or Web API controller, the scoped `ITokenProvider` is injected into the interceptor.
176+
* Configures `AddCallCredentials` for this client. The delegate is executed each time a call is made and adds the token returned by `ITokenProvider` to the metadata.
170177

171178
### Client certificate authentication
172179

@@ -360,70 +367,78 @@ private static GrpcChannel CreateAuthenticatedChannel(string address)
360367

361368
#### Bearer token with gRPC client factory
362369

363-
gRPC client factory can create clients that send a bearer token using `ChannelCredentials`. When configuring a client, assign the `CallCredentials` the client should use with the `ConfigureChannel` method.
370+
gRPC client factory can create clients that send a bearer token using `AddCallCredentials`. This method is available in [Grpc.Net.ClientFactory](https://www.nuget.org/packages/Grpc.Net.ClientFactory) version 2.46.0 or later.
371+
372+
The delegate passed to `AddCallCredentials` is executed for each gRPC call:
364373

365374
```csharp
366375
services
367376
.AddGrpcClient<Greeter.GreeterClient>(o =>
368377
{
369378
o.Address = new Uri("https://localhost:5001");
370379
})
371-
.ConfigureChannel(o =>
380+
.AddCallCredentials((context, metadata) =>
372381
{
373-
var credentials = CallCredentials.FromInterceptor((context, metadata) =>
382+
if (!string.IsNullOrEmpty(_token))
374383
{
375-
if (!string.IsNullOrEmpty(_token))
376-
{
377-
metadata.Add("Authorization", $"Bearer {_token}");
378-
}
379-
return Task.CompletedTask;
380-
});
381-
382-
o.Credentials = ChannelCredentials.Create(new SslCredentials(), credentials);
384+
metadata.Add("Authorization", $"Bearer {_token}");
385+
}
386+
return Task.CompletedTask;
383387
});
384388
```
385389

386-
A gRPC interceptor can also be used to configure a bearer token. An advantage to using an interceptor is the client factory can be configured to create a new interceptor for each client. This allows an interceptor to be [constructed from DI using scoped and transient services](/dotnet/core/extensions/dependency-injection#service-lifetimes).
390+
Dependency injection (DI) can be combined with `AddCallCredentials`. An overload passes `IServiceProvider` to the delegate, which can be used to get a service [constructed from DI using scoped and transient services](/dotnet/core/extensions/dependency-injection#service-lifetimes).
387391

388392
Consider an app that has:
393+
389394
* A user-defined `ITokenProvider` for getting a bearer token. `ITokenProvider` is registered in DI with a scoped lifetime.
390395
* gRPC client factory is configured to create clients that are injected into gRPC services and Web API controllers.
391396
* gRPC calls should use `ITokenProvider` to get a bearer token.
392397

393398
```csharp
394-
public class AuthInterceptor : Interceptor
399+
public interface ITokenProvider
395400
{
396-
private readonly ITokenProvider _tokenProvider;
397-
398-
public AuthInterceptor(ITokenProvider tokenProvider)
399-
{
400-
_tokenProvider = tokenProvider;
401-
}
402-
403-
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
404-
TRequest request,
405-
ClientInterceptorContext<TRequest, TResponse> context,
406-
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
401+
Task<string> GetTokenAsync();
402+
}
403+
404+
public class AppTokenProvider : ITokenProvider
405+
{
406+
private string _token;
407+
408+
public async Task<string> GetTokenAsync()
407409
{
408-
context.Options.Metadata.Add("Authorization", $"Bearer {_tokenProvider.GetToken()}");
409-
return continuation(request, context);
410+
if (_token == null)
411+
{
412+
// App code to resolve the token here.
413+
}
414+
415+
return _token;
410416
}
411417
}
412418
```
413419

414420
```csharp
421+
services.AddScoped<ITokenProvider, AppTokenProvider>();
422+
415423
services
416424
.AddGrpcClient<Greeter.GreeterClient>(o =>
417425
{
418426
o.Address = new Uri("https://localhost:5001");
419427
})
420-
.AddInterceptor<AuthInterceptor>(InterceptorScope.Client);
428+
.AddCallCredentials(async (context, metadata, serviceProvider) =>
429+
{
430+
var provider = serviceProvider.GetRequiredService<ITokenProvider>();
431+
var token = await provider.GetTokenAsync();
432+
metadata.Add("Authorization", $"Bearer {token}");
433+
}));
421434
```
422435

423-
The preceeding code:
424-
* Defines `AuthInterceptor` which is constructed using the user defined `ITokenProvider`.
436+
The preceding code:
437+
438+
* Defines `ITokenProvider` and `AppTokenProvider`. These types handle resolving the authentication token for gRPC calls.
439+
* Registers the `AppTokenProvider` type with DI in a scoped lifetime. `AppTokenProvider` caches the token so that only the first call in the scope is required to calculate it.
425440
* Registers the `GreeterClient` type with client factory.
426-
* Configures the `AuthInterceptor` for this client using `InterceptorScope.Client`. A new interceptor is created for each client instance. When a client is created for a gRPC service or Web API controller, the scoped `ITokenProvider` is injected into the interceptor.
441+
* Configures `AddCallCredentials` for this client. The delegate is executed each time a call is made and adds the token returned by `ITokenProvider` to the metadata.
427442

428443
### Client certificate authentication
429444

aspnetcore/grpc/clientfactory.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,28 @@ builder.Services
136136
>
137137
> These values can be overriden by `ConfigureChannel`.
138138
139+
## Call credentials
140+
141+
An authentication header can be added to gRPC calls using the `AddCallCredentials` method:
142+
143+
```csharp
144+
builder.Services
145+
.AddGrpcClient<Greeter.GreeterClient>(o =>
146+
{
147+
o.Address = new Uri("https://localhost:5001");
148+
})
149+
.AddCallCredentials((context, metadata) =>
150+
{
151+
if (!string.IsNullOrEmpty(_token))
152+
{
153+
metadata.Add("Authorization", $"Bearer {_token}");
154+
}
155+
return Task.CompletedTask;
156+
});
157+
```
158+
159+
For more information about configuring call credentials, see [Bearer token with gRPC client factory](xref:grpc/authn-and-authz#bearer-token-with-grpc-client-factory).
160+
139161
## Deadline and cancellation propagation
140162

141163
gRPC clients created by the factory in a gRPC service can be configured with `EnableCallContextPropagation()` to automatically propagate the deadline and cancellation token to child calls. The `EnableCallContextPropagation()` extension method is available in the [Grpc.AspNetCore.Server.ClientFactory](https://www.nuget.org/packages/Grpc.AspNetCore.Server.ClientFactory) NuGet package.

aspnetcore/grpc/configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ The following table describes options for configuring gRPC channels:
6969
| `Credentials` | `null` | A `ChannelCredentials` instance. Credentials are used to add authentication metadata to gRPC calls. |
7070
| `CompressionProviders` | gzip | A collection of compression providers used to compress and decompress messages. Custom compression providers can be created and added to the collection. The default configured providers support **gzip** compression. |
7171
| `ThrowOperationCanceledOnCancellation` | `false` | If set to `true`, clients throw <xref:System.OperationCanceledException> when a call is canceled or its deadline is exceeded. |
72+
| `UnsafeUseInsecureChannelCallCredentials` | `false` | If set to `true`, `CallCredentials` are applied to gRPC calls made by an insecure channel. Sending authentication headers over an insecure connection has security implications and shouldn't be done in production environments. |
7273
| `MaxRetryAttempts` | 5 | The maximum retry attempts. This value limits any retry and hedging attempt values specified in the service config. Setting this value alone doesn't enable retries. Retries are enabled in the service config, which can be done using `ServiceConfig`. A `null` value removes the maximum retry attempts limit. For more information about retries, see <xref:grpc/retries>. |
7374
| `MaxRetryBufferSize` | 16 MB | The maximum buffer size in bytes that can be used to store sent messages when retrying or hedging calls. If the buffer limit is exceeded, then no more retry attempts are made and all hedging calls but one will be canceled. This limit is applied across all calls made using the channel. A `null` value removes the maximum retry buffer size limit. |
7475
| `MaxRetryBufferPerCallSize` | 1 MB | The maximum buffer size in bytes that can be used to store sent messages when retrying or hedging calls. If the buffer limit is exceeded, then no more retry attempts are made and all hedging calls but one will be canceled. This limit is applied to one call. A `null` value removes the maximum retry buffer size limit per call. |

0 commit comments

Comments
 (0)