MagicOnion icon indicating copy to clipboard operation
MagicOnion copied to clipboard

[bug] JWT Authentication fails on second request despite token not being expired

Open phields opened this issue 1 year ago • 2 comments

Hello,

I've encountered an issue with JWT authentication in the gRPC client demo. The problem occurs when making multiple requests using the same IGreeterService client. Here's a detailed description of the issue:

  1. The first call to greeterClient.HelloAsync() succeeds and authenticates correctly.
  2. Any subsequent calls to greeterClient.HelloAsync() fail with a 401 Unauthorized error.
  3. This happens even though the JWT token has not expired (I've verified this by logging the token and its expiration time).
  4. The only way to make a successful second call is to create a new MagicOnionClient instance before each HelloAsync() call.

Here's a simplified version of the code that demonstrates the issue:

var channel = GrpcChannel.ForAddress("https://localhost:5001");
var greeterClient = MagicOnionClient.Create<IGreeterService>(channel, new[] { new WithAuthenticationFilter(signInId, password, channel), });

// This call succeeds
Console.WriteLine($"[IGreeterService.HelloAsync] {await greeterClient.HelloAsync()}");

// This call fails with 401 Unauthorized
Console.WriteLine($"[IGreeterService.HelloAsync] {await greeterClient.HelloAsync()}");

// Creating a new client for each call works, but seems inefficient
var newGreeterClient = MagicOnionClient.Create<IGreeterService>(channel, new[] { new WithAuthenticationFilter(signInId, password, channel), });
Console.WriteLine($"[IGreeterService.HelloAsync] {await newGreeterClient.HelloAsync()}");

Could you please advise on what might be causing this behavior and how to correctly handle multiple authenticated requests using the same client instance?

phields avatar Jul 21 '24 09:07 phields

Hi, could you provide a sample project or share your authentication filter, along with how you're verifying your tokens? I don't think your issue is related to MagicOnion. I'm using LitJWT (you can find it here: https://github.com/Cysharp/LitJWT) and have successfully verified my tokens each time.

You can find my implementation in the following repository: https://github.com/licentia88/MagicOnionGenericTemplate

licentia88 avatar Oct 10 '24 06:10 licentia88

I'm very sorry, but it seems that there is a bug in the sample code. If you fix WithAuthenticationFilter.SendAsync as follows, it will work.

public async ValueTask<ResponseContext> SendAsync(RequestContext context, Func<RequestContext, ValueTask<ResponseContext>> next)
{
    if (AuthenticationTokenStorage.Current.IsExpired)
    {
        Console.WriteLine($@"[WithAuthenticationFilter/IAccountService.SignInAsync] Try signing in as '{_signInId}'... ({(AuthenticationTokenStorage.Current.Token == null ? "FirstTime" : "RefreshToken")})");

        var client = MagicOnionClient.Create<IAccountService>(_channel);
        var authResult = await client.SignInAsync(_signInId, _password);
        if (!authResult.Success)
        {
            throw new Exception("Failed to sign-in on the server.");
        }
        Console.WriteLine($@"[WithAuthenticationFilter/IAccountService.SignInAsync] User authenticated as {authResult.Name} (UserId:{authResult.UserId})");

        AuthenticationTokenStorage.Current.Update(authResult.Token, authResult.Expiration); // NOTE: You can also read the token expiration date from JWT.

        if (context.CallOptions.Headers?.FirstOrDefault(x => string.Equals(x.Key, "Authorization", StringComparison.OrdinalIgnoreCase)) is {} entry)
        {
            context.CallOptions.Headers?.Remove(entry);
        }
    }

    if (!context.CallOptions.Headers?.Any(x => string.Equals(x.Key, "Authorization", StringComparison.OrdinalIgnoreCase)) ?? false)
    {
        context.CallOptions.Headers?.Add("Authorization", "Bearer " + AuthenticationTokenStorage.Current.Token);
    }

    return await next(context);
}

mayuki avatar Oct 15 '24 10:10 mayuki

This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 30 days.

github-actions[bot] avatar Apr 14 '25 00:04 github-actions[bot]