[bug] JWT Authentication fails on second request despite token not being expired
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:
- The first call to
greeterClient.HelloAsync()succeeds and authenticates correctly. - Any subsequent calls to
greeterClient.HelloAsync()fail with a 401 Unauthorized error. - This happens even though the JWT token has not expired (I've verified this by logging the token and its expiration time).
- The only way to make a successful second call is to create a new
MagicOnionClientinstance before eachHelloAsync()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?
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
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);
}
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.