Please provide an example of the Interceptor class using *async* methods
See original GitHub issueHi,
I am trying to implement JWT token authentication for a gRPC client. I’ve read the documentation and I think the “Interceptor” approach is the most promising.
Unfortunately, the documentation seems to be out of date:
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(...)
{
[..]
context.Options.Metadata.Add("Authorization", $"Bearer {_tokenProvider.GetToken()}");
[..]
}
There is no property with name Metadata
. Instead there is a Headers
property - but its content is null
. I could do this:
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(...)
{
// replace context if it comes without meta data ...
var ctx = context.Options.Headers is null
? new ClientInterceptorContext<TRequest, TResponse>(
method: context.Method,
host: context.Host,
options: context.Options.WithHeaders(new Metadata()))
: context;
// add JWT token
ctx.Options.Headers.Add("Authorization", "Bearer <insert token here>");
return continuation(request, ctx);
}
This will work - but it has two fundamental problems.
- JWT tokens have an expiration time. It should renew the token once the expiration time is reached. Of course, this should only happen if RPC methods are actively used.
- The overwritten method is synchronous.
Suppose I have a class IJwtProvider
defined like this:
public interface IJwtProvider
{
Task<string> GetToken(CancellationToken cancellationToken);
}
To keep it as simple as possible, the provider’s only job is to request, cache & update JWT tokens. No fiddling with scopes etc.
Here is the code I’ve written to archive this: (inspired by this StackOverflow question)
public class JwtAuthInterceptor : Interceptor
{
private readonly IJwtProvider _jwtProvider;
public JwtAuthInterceptor(IJwtProvider jwtProvider) {
_jwtProvider = jwtProvider;
}
/// <inheritdoc />
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
// replace context if it comes without meta data ...
var ctx = context.Options.Headers is null
? new ClientInterceptorContext<TRequest, TResponse>(
method: context.Method,
host: context.Host,
options: context.Options.WithHeaders(new Metadata()))
: context;
var ctn = continuation(request, ctx);
var task = AttachJwtToken(
responseAsync: ctn.ResponseAsync,
metadata: ctx.Options.Headers,
cancellationToken: ctx.Options.CancellationToken);
// wrap request in order to attach the JWT bearer token to the authentication header
return new AsyncUnaryCall<TResponse>(
responseAsync: task,
responseHeadersAsync: ctn.ResponseHeadersAsync,
getStatusFunc: ctn.GetStatus,
getTrailersFunc: ctn.GetTrailers,
disposeAction: ctn.Dispose);
}
private async Task<TResponse> AttachJwtToken<TResponse>(
Task<TResponse> responseAsync,
Metadata metadata,
CancellationToken cancellationToken)
where TResponse : class
{
// request bearer token (JWT)
var token = await _jwtProvider.GetToken(cancellationToken: cancellationToken);
// add access token to authorization header
metadata.Add("Authorization", $"Bearer {token}");
return await responseAsync;
}
}
This will not work and I have no idea why. For some reason, the JWT token is not added to the header of the request. I’ve searched Stackoverflow, Github, … but could not find a working example. Most blogs just copied Microsoft’s example - using a static JWT token.
I think it has something to do with the fact that ClientInterceptorContext<TRequest, TResponse>
and CallOptions
are struct
types (copy by value).
Is it possible to provide an example of “How to use Interceptor with async methods and provide metadata for the request”?
I would really appreciate that, thank you!
Issue Analytics
- State:
- Created a year ago
- Comments:5 (2 by maintainers)
Top GitHub Comments
Fixed with https://github.com/dotnet/AspNetCore.Docs/pull/25734
Interceptors don’t allow async before the call is sent. There is some unfortunate naming that
Interceptor
andAuthInterceptor
share a similar name. The concept is the same, but they’re quite different. You’ve already found the right solution which is to useAsyncAuthInterceptor
.I think there is an opportunity to make this clearer in docs.