I am using WSO2 as my Identity Provider (IDP). It is putting the JWT in an header called "X-JWT-Assertion".
To feed this into the ASP.NET Core system, I added an OnMessageReceived event. This allows me to set the token to the value supplied in the header.
Here is the code that I have to do that (the key part is the last 3 lines of non-bracket code):
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie()
.AddJwtBearer(async options =>
{
options.TokenValidationParameters =
await wso2Actions.JwtOperations.GetTokenValidationParameters();
options.Events = new JwtBearerEvents()
{
// WSO2 sends the JWT in a different field than what is expected.
// This allows us to feed it in.
OnMessageReceived = context =>
{
context.Token = context.HttpContext.Request.Headers["X-JWT-Assertion"];
return Task.CompletedTask;
}
}
};
This all works perfectly except for the very first call after the service starts up. To be clear, every call, except for the first one works exactly as I want it to. (It puts the token in and updates the User object like I need.)
But for the first call, the OnMessageReceived is not hit. And the User object in my controller is not setup.
I checked HttpContext for that first call, and the "X-JWT-Assertion" header is in the Request.Headers list (with the JWT in it). But, for some reason, the OnMessageReceived event is not called for it.
How can I get OnMessageReceived to be called for the first invocation of a service operation for my service?
IMPORTANT NOTE: I figured out that the issue was async await in AddJwtBearer. (See my answer below.) That is what I really wanted out of this question.
However, since a bounty cannot be cancled, I will still award the bounty to anyone who can show a way to use AddJwtBearer with async await where it is awaiting an actual HttpClient call. Or show documentation of why async await is not supposed to be used with AddJwtBearer.
UPDATE:
The lambda is an Action method. It does not return anything. So trying to do asynchrony in it is not possible without it being fire and forget.
Also, this method is invoked on the first call. So the answer is to call anything you need in this method in advance and cache it. (However, I have not figured out a non-hack way to use dependency injected items to make this call.) Then during the first call, this lambda will be called. At that time you should pull the values you need from cache (thus not slowing down the first call much).
This is what I finally figured out.
The lambda for AddJwtBearer does not work with async await. My call to await wso2Actions.JwtOperations.GetTokenValidationParameters(); awaits just fine, but the call pipeline goes on without waiting for AddJwtBearer to finish.
With async await the call order goes like this:
The service starts up (and you wait a while for it all to be happy.)
A call is made to the service.
AddJwtBearer is called.
await wso2Actions.JwtOperations.GetTokenValidationParameters(); is called.
GetTokenValidationParameters() invokes an HttpClient with await.
The HttpClient does an awaited call to get the public signing key of the issuer.
While the HttpClient is awaiting, the rest of the original call goes through. No events had been setup yet, so it just goes on with the call pipeline as normal.
This is where it "appears to skip" the OnMessageReceived event.
The HttpClient gets the response with the public key.
Execution of AddJwtBearer continues.
The OnMessageReceived event is setup.
A second call is made to the service
Because the event was eventually setup, the event is called. (AddJwtBearer is only called on the first call.)
So, when the await happens (in this case it eventually hits an HttpClient call to get the Issuer Signing Key), the rest of the first call goes through. Because there was no event setup yet, it does not know to call the handler.
I changed the lambda of AddJwtBearer to not be async and it worked just fine.
Notes:
Two things seem odd here:
I would have thought that AddJwtBearer would be called at startup, not on the first call of the service.
I would have thought that AddJwtBearer would not support an async lambda signature if it could not correctly apply the await.
I am not sure if this is a bug or not, but I posted it as one just in case: https://github.com/dotnet/aspnetcore/issues/20799
The reason your first couple requests cannot trigger OnMessageReceived is not because of the async void delegate you are using, but the order of how the parameters being loaded and the events being attached.
You attach handlers to events after await, meaning you created a race condition here, that, if say some request arrives before the await is completed, there is no event handler attached to OnMessageReceived at all.
To fix this, you should attach event handlers before the first await. This will guarantee that you always have event handlers attached to OnMessageReceived.
Try this code:
services.AddAuthentication(opt =>
{
// ...
})
.AddJwtBearer(async opt =>
{
var tcs = new TaskCompletionSource<object>();
// Any code before the first await in this delegate can run
// synchronously, so if you have events to attach for all requests
// attach handlers before await.
opt.Events = new JwtBearerEvents
{
// This method is first event in authentication pipeline
// we have chance to wait until TokenValidationParameters
// is loaded.
OnMessageReceived = async context =>
{
// Wait until token validation parameters loaded.
await tcs.Task;
}
};
// This delegate returns if GetTokenValidationParametersAsync
// does not complete synchronously
try
{
opt.TokenValidationParameters = await GetTokenValidationParametersAsync();
}
finally
{
tcs.TrySetResult(true);
}
// Any code here will be executed as continuation of
// GetTokenValidationParametersAsync and may not
// be seen by first couple requests
});
You can use GetAwaiter().GetResult() to execute async code in startup. It will block the thread, but it's ok because it only run once and it's in the startup of the application.
However if you don't like to block the thread and insist on using await to get the options, you can use async await in Program.cs to get your options and store it in a static class and use it in startup.
public class Program
{
public static async Task Main(string[] args)
{
JwtParameter.TokenValidationParameters = await wso2Actions.JwtOperations.GetTokenValidationParameters();
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
public static class JwtParameter
{
public static TokenValidationParameters TokenValidationParameters { get; set; }
}
Related
So I have the problem that when many Reactions get added to a Message in discord I get the following error:
A ReactionAdded handler is blocking the gateway task.
As long as only a few reactions are added everything seems fine but when multiple are added at the same time (or in quick succession) I get the error.
It also seems in general that the ReactionHandler is taking time to realise there was a reaction added. Which should not be because the things I do in the handler are slow (because I don't do much in there)
Necessary Code (There's more but for this problem it's unnecessary I think):
class Program
{
//some variables
public static Task Main() => new Program().MainAsync();
public async Task MainAsync()
{
using IHost host = Host.CreateDefaultBuilder()
//some other code
.AddSingleton<ReactionHandler>())
.Build();
await.RunAsync(host);
}
public async Task RunAsync(IHost host)
{
using IServiceScope serviceScope = host.Services.CreateScope();
IServiceProvider provider = serviceScope.ServiceProvider;
//some other code
var reactions = provider.GetRequiredService<ReactionHandler>();
reactions.InitializeAsync();
//some other code
await _client.LoginAsync(TokenType.Bot, "Token");
await _client.StartAsync();
await Task.Delay(-1);
}
}
public class ReactionHandler
{
private readonly DiscordSocketClient _client;
public ReactionHandler(DiscordSocketClient client)
{
_client = client;
}
public async Task InitializeAsync()
{
_client.ReactionAdded += HandleReationAsync;
_client.ReactionRemoved += HandleReactionAsync;
}
private async Task HandleReactionAsync(Cacheable<IUserMessage, ulong> message, Cacheable<IMessageChannel, ulong> channel, SocketReaction reaction)
{
if (reaction.User.Value.IsBot) return;
Console.WriteLine("Reaction changed");
//some other code
}
}
So if there's some information missing (cause I left out some code) just tell me then I add it. Now in the interent I've read simular things happen with the MessageReceived Handler, but their code was so different from mine that I just didn't understand it. Also I've read that this can happen when the code / things I do are slow, but the code above is not that much that it is too slow right.
I hope everything's clear and thanks in advance :)
If I understand the problem correctly, seems like you're eating up all the threads in your app to handle reactions. It might be better to make the ReactionHandler a hosted service then queue reactions to that service so you're not waiting on those threads. This will essentially make ReactionHandler run in the background and process as they come.
Background tasks with hosted services in ASP.NET Core
Just do keep in mind that this will be on a completely separate thread so you need to make adjustments to make it a multithreaded app.
You would convert your handler to a hosted service, use some message queueing solution or roll your own to make requests to the service. Then the client (outside of the hosted service) would then add to the queue for when reactions are received.
I have a WebSocket that sends me events rapidly whenever there is a new message available, the event call for this looks like this:
Task.Factory.StartNew(() => _onStringMessage(e.RawData));
public async void _onStringMessage(var eventSent)
{
... // business logic
var variable = await _httpClient.getAsync(eventSent.Url);
... // business logic
var success = await _httpClient.postAsync(eventSent.Url + "/arg");
... // business logic
}
This code runs fine for a few iterations and then get a "Task was cancelled" exception error on either the GET or the POST request. The _httpClient is a standard .NET HttpClient wrapped in a IHttpClient class declared as a class variable. However, if I add a Semiphore like below, everything runs perfect:
public async void _onStringMessage(var eventSent)
{
await semiphore.WaitAsync();
... // business logic
var variable = await _httpClient.getAsync(eventSent.Url);
... // business logic
var success = await _httpClient.postAsync(eventSent.Url + "/arg");
... // business logic
semiphore.Release();
}
I am using .NET 4.6.1 and as far as I understood, both of these HTTP methods should be thread safe. Both methods ends up in a SendAsync method via the HTTP handler but that should also be thread safe. So I do not understand why I get the Task was cancelled exception? The only thing I can think of is that the server rejects the calls because too many requests at the same time, but then I imagine I would get some other error, like HTTP BadRequest or similar. This method runs approximately 10-20 times per second. My timeout is set to 1 second and all request are so far only on localhost.
I have two applications using Rebus in ASP.NET MVC Core
I am able send messages between two applications using Bus.Send(...). What I can't is to publish event such as CustomerCreated after creating so that other applications can take actions.
I have configured the application as follows
public void ConfigureServices(IServiceCollection services)
{
services.AutoRegisterHandlersFromAssemblyOf<Handler1>();
services.AddRebus(configure => configure
.Logging(l => l.Use(new MSLoggerFactoryAdapter(_loggerFactory)))
.Transport(t=>t.UseRabbitMq("amqp://guest:guest#localhost:5672", "rebus_rabbit_first"))
.Sagas(x => x.StoreInSqlServer("Data Source=.;Initial Catalog=RebusDBRabbit;User ID=student;Password=student;", "Sagas", "SagaIndex"))
.Options(o =>
{
o.SetNumberOfWorkers(10);
o.SetMaxParallelism(20);
o.HandleMessagesInsideTransactionScope();
o.SimpleRetryStrategy(errorQueueAddress: "somewhere_else", maxDeliveryAttempts: 10, secondLevelRetriesEnabled: true);
})
.Routing(r => r.TypeBased()
.MapAssemblyOf<CreateStudent>("rebus_rabbit_second")));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
In the Controller I Send a message to another Application as follows
CreateStudent student = new CreateStudent { StudentID="90008", StudentName="Amour Rashid Hamad",DateOfBirth=DateTime.Parse("1974-03-18") };
_bus.Send(student).Wait();
This is OK.
Now My Problem is to publish an event to broadcast the event to other interested parties eg
_bus.Publish(new StudentCreated { StudentID="90008",Remarks="Hurray We have been Successfully"});
How Do I Subscribe to the event as per my configuration. I have seen some samples but I could not understand them. Adding to my implementation would be preferred.
In the Service Configuration I did as follows:
app.ApplicationServices.UseRebus(async bus => {
await bus.Subscribe<StudentCreated>();
});
and then created a handler
public class StudentCreatedEventHandler : IHandleMessages<StudentCreated>, IHandleMessages<IFailed<StudentCreated>>
{
readonly IBus _bus;
public StudentCreatedEventHandler(IBus bus)
{
_bus = bus;
}
public async Task Handle(StudentCreated student)
{
// do stuff that can fail here...
var remarks = $"Remarks on RebusWithRabbit1 : {student.Remarks}";
}
public async Task Handle(IFailed<StudentCreated> failedMessage)
{
await _bus.Advanced.TransportMessage.Defer(TimeSpan.FromSeconds(30));
}
}
This could handle the events published.
I just want to get assured if that is the proper way of doing it.
I have however noticed one thing. If I have more than one endpoints Subscribing to the event only one is notified. I expected that multiple endpoints could need to be notified and every one may execute a different process from the same event.
Is there any way to change this behavior. I remember in MassTransit this is the default behavious.
Thanks
It looks like you're using await bus.Send(...) properly.
As you've probably figured out, Send looks up a destination queue and sends to that (and only that), and the lookup is done from the endpoint mappings (where you're currently mapping all message types to the queue named rebus_rabbit_second).
When you want to await bus.Publish(...), you need someone to await bus.Subscribe<T>() accordingly. Underneath the covers, Rebus will use the .NET type as the topic, so if you
await bus.Subscribe<SomeEvent>();
in one application, and
await bus.Publish(new SomeEvent());
in another, your subscriber will receive the event.
TL;DR: You need to
await bus.Subscribe<StudentCreated>();
in the application where you want to receive published events of type StudentCreated.
Btw. you should EITHER use C#'s support for calling asynchronous methods
await bus.Send(yourMessage);
or invoke Rebus' synchronous API to do your work:
var syncBus = bus.Advances.SyncBus;
syncBus.Send(yourMessage); //< returns void
Rebus' synchronous methods do not deadlock the thread, e.g. if you're calling them from ASP.NET or WCF.
Given:
A legacy non-async API method on an ASP.NET/WCF web service
New async internal library
New async Web API controller that should be used going forward
A "storage provider" object that only has an async interface. Its tests pass when run asynchronously, and when run synchronously outside a request context.
The option "go async all the way" is not on the table, since it would break backward compatibility.
public class Impl {
// This works fine when used asynchronously
public Task<Guid> SaveThingAsync(Thing thingToSave) {
return await _storageProvider.saveAsync(thingToSave);
}
public Guid SaveThing(Thing thingToSave) {
// "Obviously", this code creates a deadlock when called
// from within the request context
// return SaveThingAsync(thingToSave).Result
// Not so obviously, this also creates a deadlock
// return SaveThingAsync(thingToSave)
// .ConfigureAwait(false)
// .GetAwaiter()
// .GetResult()
// This was deadlocking, but magically stopped
// return Task.Run(
// async () => await SaveThingAsync(thingToSave)
// .ConfigureAwait(false)
// ).Result;
// This one works
var saveTask = Task.Run(async () =>
await SaveThingAsync(thingToSave)));
var result = saveTask.ConfigureAwait(false).GetAwaiter().GetResult();
return result;
}
Why?
Task.Run steps "outside" the request context - it just runs on the thread pool context. So, it won't deadlock because SaveThingAsync doesn't resume on the request context.
On a side note, the ConfigureAwait(false) is meaningless there, since there is no await to configure.
On another side note, "async all the way" should still be an option. WebAPI and WCF clients don't care whether the implementation they're calling is synchronous or asynchronous. Changing a WCF method implemented synchronously to a WCF method implemented asynchronously is invisible to client code.
I have a process where an incoming user request to our system is being handled. I also want to add some metadata about the request to a database table without impacting the responsiveness of the main process. To achieve this I added a call to an asynchronous method like this:
public static ReturnObject ResponsiveMethod(string ip, string code)
{
// ... some reasonably quick code
IPDetail.InsertAsync(ip); // <-- call to the async method
return new ReturnObject(code);
}
The InsertAsync() method looks like this:
public static void InsertAsync(string ipAddress)
{
Action action = () => IPDetail.Insert(ipAddress);
action.BeginInvoke(aResult => Log.Debug("Completed Insert"), null);
}
And finally, the normally non-asynchronous method called Insert():
private static void Insert(string ipAddress)
{
ApplicationContextHelper.LoadApplicationContext();
var helper = new GeoLocationHelper();
var result = helper.GetDetailsFromIP(ipAddress);
Log.InfoFormat("Succesfully retreived IP data {0}.", ipAddress);
result.Save();
}
In my unit tests the InsertAsync() call works perfectly. Inside the method calls in Insert() there are many operations occuring which are detailed by logging, and all the expected log messages are there, as well as the final result of the result.Save() method.
However, we have a webservice which utilizes something like the ResponsiveMethod() method above and for some reason the asynchronous calls do not complete. All of the logging in the LoadApplicationContext() method gets fired, but after that there is no log activity related to the Insert() and the result.Save() is never getting executed.
Revised summary question to be more concise
My current thinking is that the webservice has completed its task and the thread which called the asynchronous no longer exists. Would this stop the async call from completing?
I've never used BeginInvoke before, but usually where there's a Begin*, you also need the coresponding End*. Please add one, along with correct exception handling.
My first thought is that you may be throwing an exception on your async call in the web service scenario for some reason. I know you've probably pared it down to post it on the web, but is there any "more-or-less unfailable" error handling code in there?
Are you relying on the identity of the caller in the Async method call? The identity may be lost when called from the web service.