Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
phields opened this issue Jul 21, 2024 · 2 comments

Comments

@phields
Copy link

phields commented Jul 21, 2024

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?

@licentia88
Copy link

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

@mayuki
Copy link
Member

mayuki commented Oct 15, 2024

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);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants