Inject IPrincipal with SignalR - c#

I am trying to inject IPrincipal into my SignalR hub constructors.
I already saw and tried the solution from "Selective IPrincipal Injection via StructureMap with SignalR", but unfortunately that doesn't seem to work anymore for SignalR 2.x.
In my debugging, I've discovered that sometimes, my hub constructor is called with my OWIN middleware in the stack. When that is the case, Thread.CurrentPrincipal is the correct value. Additionally (and surprisingly), HttpContext.Current is also not-null. I was under the impression this was always null in SignalR, and I'm not attempting to use it, but I'm just observing. It seems like these calls that do work come from the pipeline in the call stack.
Other times, calls seem to come from the thread pool. In those instances, Thread.CurrentPrincipal is a GenericPrincipal, HttpContext.Current is null (again just observing), and I cannot seem to get at the principal statically. However, inside the hub, the this.Context.User property does have the right principal.
How else can I get the principal statically so that I can inject it into the hub constructor?

It is expected that HttpContext.Current and Thread.CurrentPrincipal will sometimes be set when SignalR Hubs get activated, but not always. This is because the activating thread often runs with ASP.NET's SynchronizationContext. There are situations when this isn't the case, such as when a Hub is activated in order to handle a WebSocket message or an unclean OnDisconnected event. Long story short, sometimes these statics happen to be there, but you cannot rely on it.
I don't know of any way to statically get the IPrincipal reliably. What's wrong with using Context.User inside your Hub?

If I understood correctly what you're trying to do... You should build your own Authorize attribute, that will put the custom principal into a special Owin var, then it will be accessible in Context.User inside a hub.
public class MyAuthorizeAttribute : AuthorizeAttribute
{
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
//put our custom user-principal into a magic "server.User" Owin variable
request.Environment["server.User"] = new MyCustomPrincipal(); //<!-THIS!
return base.AuthorizeHubConnection(hubDescriptor, request);
}
}
And then apply this attribute to your Hub.
If you want more info on this, I blogged about this here with more code samples

I was trying to solve the same problem and I found solution to set the identity of the user.
My application is saml protected and client application sends "SAML" token as part of header. We have written Asp.net module to verify the token and prepare identity of the user and add value to the response Headers.
I have created OwinStartup class and I have added my own request processor by using below code.
I have tested this piece of code for Longpolling and working fine. I am not sure how it works in "WebScoket".
public void Configuration(IAppBuilder app)
{
// Any connection or hub wire up and configuration should go here
try
{
app.Use(SetMyPrincipalObject);
}
}
private Task SetMyPrincipalObject(IOwinContext arg1, Func<Task> arg2)
{
//var p = "Process response";//Process Response Header here and //create identity
//arg1.Request.User = p;
//return arg2.Invoke();
}

Related

What is the proper way of getting the HTTP request method in an AuthorizationHandler<T>?

Simplified version of what I'm trying to do: I want to write an authorization policy that will block requests that aren't GET if a flag is set in the database. I registered my policy, the handler code runs fine for my controller, but I'm not sure what the best way of getting the HTTP method type is.
Controller looks like this:
[Authorize(Policy = "Test")]
public class MyController : ControllerBase
{
// ...
}
My handler looks like this:
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext ctx, MyRequirement req)
{
// Fetch some flag from db
if (!flag)
{
ctx.Succeed(req);
return;
}
var method = GetRequestMethodFromCtx(ctx.Resource);
if (method == HttpMethods.Get)
{
ctx.Succeed(req);
}
else
{
ctx.Fail();
}
}
I noticed that for a single request my handler gets called multiple times and that ctx.Resource is not always the same type. First time it is a RouteEndpoint, after which it is an AuthorizationFilterContextSealed.
Should I just extract the HTTP method from the RouteEndpoint and ignore the second call? Also, why are there multiple calls to the handler?
In order to access to HttpContext object you can use the IHttpContextAccessor service.
You can simply require it as a dependency of your authorization handler class and the ASP.NET core dependency injection will provide the service for you.
In order to register it with the dependency injection you need to call services.AddHttpContextAccessor() in the ConfigureServices method of your Startup class.
From the HttpContext you will have access to the Request and then to the Method property (see here).
Refer to the official documentation for the details.
I don't know exactly your requirements, but in order to prevent an action method being called with a specific HTTP verb there is a much simpler way. You just need to use the built in routing attributes, you can find more information here.

SignalR: GetHubContext does not call client methods, but referencing the hub directly does

I had a troubling issue dealing with SignalR (v 2.4.1) hubs last week, and despite doing enough by the documents, I could not broadcast messages without hacking my way through it. For some extra context, this is a self hosted (Owin) hub attached to a windows service.
MSDN documents, and runtime errors (Using a Hub instance not created by the HubPipeline is unsupported), suggest that we are supposed to get the hub context by calling GlobalHost.ConnectionManager.GetHubContext<ThisTypeOfHub>(), and at that point we can make calls to the client.
(https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-server#callfromoutsidehub)
When I originally set up calls to the clients, I was doing it this way:
public void OnDisplayMessage(string message)
{
var hub = GlobalHost.ConnectionManager.GetHubContext<ThisTypeOfHub>();
hub.Clients.All.BroadcastToClient(message);
}
The browsers that contained the method and had connected to signalR never got called, however. The clients could call server methods, and even the server methods would send out a callback which the clients responded to, but the hub context could never call out to the client methods when called from outside the hub. In the end, I directly brought back the hub reference in the IoC container, and called out to clients using that, as shown below.
public class LogicWithUI : Logic
{
Hub hub;
public LogicWithUI(IDependencyInjectionContainer container)
{
this.hub = container.Resolve<ThisTypeOfHub>(); // ThisTypeOfHub inherits from Hub
}
public class OnDisplayMessage(string message)
{
try
{
this.hub.Clients.All.DisplayMessage(string);
}
catch (Exception)
{
//do nothing, no webpage has connected yet
}
}
}
This way it is finally working fine (you can see the catch placed there for when an error occurs -- only when no webpages have connected to this yet), but it doesn't make sense.
What would cause the HubContext called from outside the hub to not actually broadcast the method?
Why would the unsupported error get thrown only when no clients are connected?
Are there any obvious mistakes I'm overlooking here?
The primary goal here is to have a functioning product, but I also want to do it the right/documented way. It's a little confusing when that way isn't working.
I also understand I might have left out some important details regarding the SignalR configuration, I can answer any follow up questions, but wanted to start with the basic explanation.

Why does CustomTokenRequestValidator service get executed when grant_type="code"?

Expected behavour
If the user logged in via your SPA web app, and the app uses grant_type=code it is expected that only "ProfileDataRequestContext" service would get executed.
However it also executes ICustomTokenRequestValidator service, why is that? am I doing something wrong?
public class ProfileService : IProfileService
{
//this should be executed only when grant_type=code
}
-
public class CustomTokenRequestValidatorService : ICustomTokenRequestValidator
{
//this should only be executed when grant_type=clientcredentials (however it always gets executed at all times)
}
If you look into the flow, described by the spec, you could see that code flow consists of at least two calls to Authorization Server, and the second one is the call to Token Endpoint, triggering the TokenRequestValidator invocation, as well as each call to the AuthorizationEndpoint triggers the AuthorizeRequestValidator correspondingly.
For SPA and native apps that does not matter, but for MVC apps the context of the calls to Authorization and Token endpoints differs a lot: the first one is performed in the context of the browser, so contains some browser-specific headers, such as locale, when the second one is a server-to-server (back channel) call.
Regarding your sub-question about IProfileService invocation: that takes place each time the token or response contains UserClaims. When you request id_token, access_token and then retrieve some additional data from UserInfo endpoint, your IProfileService might be called three times.
ICustomAuthorizeRequestValidator is called for every grant type as per the source code. Having said that, you get CustomTokenRequestValidationContext passed into the ValidateAsync which has TokenRequestValidationResult which has ValidatedTokenRequest which in turn has GrantType property so if you aim to only run some code on client credentials - a simple if statement should suffice:
public async Task ValidateAsync(CustomTokenRequestValidationContext context)
{
if (context.Result.ValidatedRequest.GrantType == "client_credentials")
{
...your logic
}
}

Pass a parameter value to the client

I have a button, that when clicked will/should notify the server. The server will then save a value to the database. If all went well, it should return true, otherwise return false.
I instantiate a hub in my view
var signalRhub = $.connection.hubSignalR;
Start the connection:
$.connection.hub.start().done(function () {
$("#submitBut").click(function () {
signalRhub.server.cardAdded();
});
});
Define the function that will be used by the server to return the boolean value:
signalRhub.client.cardAddedRes = function (isSuccess) {
alert("From server: " + isSuccss);
}
My Hub class:
public class HubSignalR : Hub
{
public bool isSuccess = false; <-- Will be set from controller
public void CardAdded()
{
Clients.Caller.CardAddedRes(isSuccess); <-- Notice the isSuccess
}
}
My problem is that the isSuccess value is coming from my controller, that interacts with the model/database.
So I get the error:
Using a Hub instance not created by the HubPipeline is unsupported.
I tried using: GlobalHost.ConnectionManager.GetHubContext<HubSignalR>()
but I can't make it work.
Here is the relevant code in my controller:
private HubSignalR signalR = new HubSignalR(); <-- Field variable
[HttpPost]
public ActionResult AttachCard(Card model, int MemberID)
{
var hub = GlobalHost.ConnectionManager.GetHubContext<HubSignalR>();
...
//We saved to the database, so we call the client function with bool = true
hub.Clients.All.CardAdded(true); <-- Actually I want to send to one client, NOT ALL
//Something like hub.Clients.Caller.CardAdded();
}
I'm forced to make the isSuccess field in my: HubSignalR class, since I need to return that as the parameter from my controller. But when the button is clicked, this value has not yet been set (I think).
I can see from the debugger, that I do reach: signalRhub.server.cardAdded();
But the server never responds, so I don't reach this function:
signalRhub.client.cardAddedRes = function (isSuccess) {
alert("From server: " + isSuccss);
}
I don't really get to call the CardAdded() method from my controller, cus of the GlobalHost.ConnectionManager.GetHubContext. But you can see
If you got a nicer solution than what I'm trying to do, please tell. I'm total new with SignalR and fairly new with ASP.net MVC
Hope I got the problem right: you want to set isSuccess in controller and send it to specific client, but don't reach specific client from outside the hub?
As to "how":
You should find a way to identify your client, i.e. implement some kind of authentication. More on this here: Authentication in .net core When clients connect to SignalR, they get a connection ID. You can map the connection ID to real client identity. More info here: Mapping clients to connections
Than in you server method:
Get authenticated client identity
Get signalR hub context
Map client identity to existing signalR connection ID
Send message to that signalR connection
As I can see from you code you might be working on some personal-use/exploratory project and probably not interested in implementing authentication/don't care about security. You could get signalR connection ID in your client after connecting through $.connection.hub.id. Than you could send this ID to the server method as parameter or header. This is by no way should be used in production environment, as you would trust the client who he is, and pass parameters that are not strictly needed by your method.
As to "why":
In fact I don't think you need signalR for your use case. You call a server method, it saves to DB and returns you OK or not OK, client is happy. No need to pass it through signalR.
You do need signalR e.g. when:
- Same client is logged in on several devices and wants to get updates if changes were made on one of them
- Client works on something and another one changes same data. You want to inform the first client.
- Inform your client of an event that was not triggered by him (new notification)
In all this cases you have some kind of authentication and sending signalR message to the right client is not a problem.
Answer to comment below
I have little experience with ajax, i guess it might work. Another idea, if you want to avoid authentication, is a subscription model with SignalR.
You have to find out which specific resources you have, let's say "game" in your case, which have IDs. Than clients, interested in that particular resource, shall subscribe to changes.
All participants of a particular card game instance shall call a hub method defined like this:
public async Task SubscribeToGameChanges(long id)
{
await this.Groups.AddToGroupAsync(this.Context.ConnectionId, Helper.GetGameGroupName(id));
}
public static class Helper
{
public static string GetGameGroupName(long id)
{
return $"Game_{id}";
}
}
Than call it from client. Note the AddToGroupAsync. SignalR shall create a group with given name and add a client to it. If group exists, it will just add another client. So you have a group per game instance with a list of interested clients (players).
Now when a change happens to the game, you notify all your clients by calling from your controller on hub context:
await hubContext.Clients.Groups(Helper.GetGameGroupName(id)).SendAsync("myNotifyMethod", myParameters);
You could pack all your changes into parameters, or just inform the clients that the state of the game (or whatever other resource) has changed and clients shall requery the state through normal API call.
Also I noticed you use GlobalHost in your code. My code samples are for .net core SignalR version and might slightly differ in your case. See here on differences between .net core and full .net SignalR.

How to communicate Server to Client with SignalR in Nancy with ASP.NET Hosting?

Most of the examples I've found for SignalR are assuming ASP.NET (MVC or not). I'm using NancyFX. I'm having just one problem, so I'm hoping there's something I'm overlooking or some thing I need to do in Nancy to compensate for not being ASP.NET.
My one goal is to be able to notify the client browsers when a server event happens. I don't plan on replacing my Nancy routes with hub methods. But I would like the ability to call into the browser from my routes (actions).
I have very simple Hub that I created following the example in the SignalR Wiki. I'm not even sure I need it, since I don't plan on calling client to server.
public interface IUserNotifier
{
void Start();
void Notify(object #event);
}
I used an interface in hopes that I would be able to inject the same hub later on to use in my nancy routes... I'm not sure that is in the cards.
[HubName("userNotifier")]
public class UserNotifier : Hub, IUserNotifier
{
public void Start()
{
Notify(new {Status = "Started"});
}
public void Notify(object #event)
{
Clients.notification(#event);
}
}
When I have the following code in my html file, I can see that it executes the Start() method, and then the Notify() method, delivering content to my client.
var communicator = $.connection.userNotifier;
$.extend(communicator, {
Notification: function(event) {
alert("notification received from server!");
console.log(event);
}
});
$.connection.hub.start()
.done(function() {
communicator.start();
});
Like I said, "starting" the hub works and sends a notification to the client. Very cool. But, then, my primary goal hasn't been accomplished yet. I need to initiate these notifications from other places in my code where they might not be directly associated with a "request".
I tried injecting my IUserNotifier in my nancy modules for use in the routes, but when the Notify() is fired, I get:
That's because the Clients property on the Hub base class is null (hasn't been initialized). So, I switched gears. I tried to follow multiple examples, including the example from the wiki page about hubs in the section called "Broadcasting over a Hub from outside of a Hub":
public class NotifierModule : NancyModule
{
public NotifierModule(){
Get["/notify/{message}"] = p => {
var context = GlobalHost.ConnectionManager.GetHubContext<UserNotifier>();
context.Clients.notification(new { Message = p.message });
};
}
}
My Nancy route executes without throwing errors. Except my browser never receives the message. If I set a breakpoint in the route, I can see that Clients is initialized. Maybe the collection of clients is initialized but empty. Who knows? Maybe you do. :)
Again, my main goal is to be able to send events/notifications to the browser from anywhere in my code, any time. Is that too much to ask? What should I be doing here?
I'm sure you must have found the answer already. However, I figured I could try and help out in case anyone else runs into a similar problem. In order for your server on the .NET side to send messages to clients, it would also need to have a connection made to the hub.
var connection = new HubConnection("http://localhost/");
connection.Start();
connection.Notify("Hello");
Check out an official example at:
https://github.com/SignalR/SignalR/blob/master/samples/Microsoft.AspNet.SignalR.Client.Samples/Program.cs

Categories

Resources