question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Please provide an example of the Interceptor class using *async* methods

See original GitHub issue

Hi,

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.

  1. 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.
  2. 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:closed
  • Created a year ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
JamesNKcommented, Apr 29, 2022
1reaction
JamesNKcommented, Apr 13, 2022

Interceptors don’t allow async before the call is sent. There is some unfortunate naming that Interceptor and AuthInterceptor share a similar name. The concept is the same, but they’re quite different. You’ve already found the right solution which is to use AsyncAuthInterceptor.

I think there is an opportunity to make this clearer in docs.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Intercept the call to an async method using DynamicProxy
Below is my async interceptor adapter implementation that correctly handles async methods. abstract class AsyncInterceptor : IInterceptor { ...
Read more >
Intercepting Async Requests using AsyncHandlerInterceptor
The method AsyncHandlerInterceptor#afterConcurrentHandlingStarted is called after a new thread for async process (Callable) has been created.
Read more >
Interceptors - EF Core
Each pair of methods have both sync and async variations. ... You can download the command interceptor sample from GitHub.
Read more >
EnableAsync - spring-framework
Enables Spring's asynchronous method execution capability, similar to functionality found in Spring's <task:*> XML namespace. To be used together with @ ...
Read more >
Interceptors | NestJS - A progressive Node.js framework
Each interceptor implements the intercept() method, which takes two arguments. The first one is the ExecutionContext instance (exactly the same object as for ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found