Using SignalR, I believe I should be able to send messages to specific connected users by using UserID Provider
Does anyone have an example of how this would be implemented? I've searched and searched and can not find any examples. I would need to target a javascript client.
The use case is, users to my site will have an account. They may be logged in from multiple devices / browsers. When some event happens, I will want to send them a message.
I have not looked into SignalR 2.0 but I think this is an extension of what the previous versions of SignalR used to have. When you connect to the hub you can decorate it with an Authorize attribute
[HubName("myhub")]
[Authorize]
public class MyHub1 : Hub
{
public override System.Threading.Tasks.Task OnConnected()
{
var identity = Thread.CurrentPrincipal.Identity;
var request = Context.Request;
Clients.Client(Context.ConnectionId).sayhello("Hello " + identity.Name);
return base.OnConnected();
}
}
As you can see you are able to access the Identity of the user accessing the Hub. I believe the new capability would be nothing more than an extension of this. Since the connection is always kept alive between the client and the hub you will always have the principal identity which will give you the UserId.
I believe this can help you: (linked from here)
A specific user, identified by userId.
Clients.User(userid).addContosoChatMessageToPage(name, message);
The userId can be determined using the IUserId interface:
public interface IUserIdProvider
{
string GetUserId(IRequest request);
}
The default implementation of IUserIdProvider is PrincipalUserIdProvider. To use this default implementation, first register it in GlobalHost when the application starts up:
var idProvider = new PrincipalUserIdProvider();
GlobalHost.DependencyResolver.Register (typeof(IUserIdProvider), () => idProvider);
The user name can then be determined by passing in the Request object from the client.
Related
I've created negotiate and send message functions in Azure Functions (similar to the samples below) to incorporate the SignalR Service. I'm setting UserId on the SignalRMessage by using a custom authentication mechanism.
https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-signalr-service?tabs=csharp
[FunctionName("negotiate")]
public static SignalRConnectionInfo Negotiate(
[HttpTrigger(AuthorizationLevel.Anonymous)]HttpRequest req,
[SignalRConnectionInfo
(HubName = "chat", UserId = "{headers.x-ms-client-principal-id}")]
SignalRConnectionInfo connectionInfo)
{
// connectionInfo contains an access key token with a name identifier claim set to the authenticated user
return connectionInfo;
}
[FunctionName("SendMessage")]
public static Task SendMessage(
[HttpTrigger(AuthorizationLevel.Anonymous, "post")]object message,
[SignalR(HubName = "chat")]IAsyncCollector<SignalRMessage> signalRMessages)
{
return signalRMessages.AddAsync(
new SignalRMessage
{
// the message will only be sent to this user ID
UserId = "userId1",
Target = "newMessage",
Arguments = new [] { message }
});
}
I'd like to send a push notification if the client is no longer connected instead of adding a new object to the IAsyncCollector. I've also set up AppCenter push framework properly, but I'm facing an issue. Is there an easy way to find out which UserId is still connected to the hub? This way, I could decide to send a push. What is the recommended Microsoft guidance on this issue?
Have a look at this feature: Azure SignalR Service introduces Event Grid integration feature, where the SignalR Service emits two following event types:
Microsoft.SignalRService.ClientConnectionConnected
Microsoft.SignalRService.ClientConnectionDisconnected
More details here.
I'm implementing a straight out of the box solution using IDserver4(2.3) targeting .netcore 2.2 that communicates with a FHIR client by calling:
Url/api/openid/openapp?launch=12345t6u-34o4-2r2y-0646-gj6g123456t5&iss=myservice&launchOrganization=tilt
with some HL7 simulated scopes etc. The flow is okay all the way to the token endpoint serving access and id tokens using the quickstart on an IIS with certificates and all the bezels.
My problem lies in that the client requires a parameter to be passed to the external client pointing to a file or something on the server where I have some test patient data stored/or served as Json.
Any competent way to pass a parameter with the body or the header for example? And do you do it at the authorization or the authentication, or along with the tokens? Lets call it context. The service shut me down when i reach it. Says this on their side 'TypeError: Parameter "url" must be a string, not undefined'
Thanks in advance.
Got it using:
public class CustomClaimInjection : ICustomTokenRequestValidator
{
private readonly HttpContext _httpContext;
public CustomClaimInjection(IHttpContextAccessor contextAccessor)
{
_httpContext = contextAccessor.HttpContext;
}
public Task ValidateAsync(CustomTokenRequestValidationContext context)
{
var client = context.Result.ValidatedRequest.Client;
//client.Claims.Add(new Claim("sub", sub)); // this will be [client_sub]
context.Result.CustomResponse = new Dictionary<string, object>
{
{"example-launchcontext", "https://url/" }
};
return Task.CompletedTask;
//return Task.FromResult(0);
}
}
I think I understand your problem now, and I think you would like a successful authentication to return additional information about where the patient's file is stored. I would store this in the token as a claim since it can be expressed as a statement about the subject (the user). This can be done in the registered (through dependency injection) implementation of the IProfileService. In the implementation of 'GetProfileDataAsync' you can set the issued claims using the 'ProfileDataRequestContext' parameter's property 'IssuedClaims'. These claims will be used to populate the id token which is what you should be looking to do.
I am using SignalR with my WebApi application and that works fine: the client connects to the hub using WebSocket transport and the user is authenticated with some custom middleware authentication.
I am trying to connect to this hub from another backend application (.NET client) and to do this I make a hub connection and create a hub proxy, then invoke the hub method:
string auth = "someEncryptedValue"
HubConnection hubConn = new HubConnection("myUrl");
hubConn.Headers.Add("myauthtoken", auth);
IHubProxy proxy = hubConn.CreateHubProxy("hubName");
Task t = Task.Run(() => hubConn.Start(new LongPollingTransport()));
t.WaitAndUnwrap // An extension method
hubProxy.Invoke("SendMessage", message);
The exception is thrown when t.WaitAndUnwrap() is called (the extension method is not the issue). I had this incoming principal is null issue before I added the http header token (which, of course, is not actually the literal string "someEncryptedValue"). So I added that here and then I added a custom authorization class for the hub back in my other application:
public class HeadersAuthAttribute : AuthorizeAttribute
{
public override bool AuthroizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
string auth = request.Headers["myauthtoken"];
if (auth == "someEncryptedValue") //again, this is actually more complex than shown here
{
return true;
}
else
{
// Need to check if the incoming principal is authenticated in case
// the connection to the hub is being made the normal way through the
// WebApi instead of the proxy using http headers
return request.User?.Identity?.IsAuthenticated ?? false;
}
return false;
}
}
And of course my hub has the headers attribute:
[HeadersAuth]
public class MyHub : Hub
{
// Hub logic
}
However, after running this I still get the incoming principal is null error. Then I read that you cannot use custom http headers with WebSockets, which is why I put new LongPollingTransport() in the hubConn.Start call above. But that didn't seem to change anything, at least not the error I get.
Does anyone know what can be going on? It'd be nice if I could debug the code with the actual hub and authorization so I can see what's going on when the hubConn.Start call is made. Is there a way I can check the http header is set correctly and fetched correctly? If the error is about the incoming principal being null, is authentication even the issue? Could it be another part of the code where it's trying to find the user? I'm not sure what to do about that since this hub connection is being made from a .NET client. Also, I know the HeadersAuthAttribute class is being called and is used correctly when connecting to the hub normally using WebSockets since it goes into the else case where it checks that the IIdentity is authenticated.
Just to add a bit more of what I've tried:
I made the auth string token purposefully wrong to see if I get a different error, but I still get incoming principal is null.
I realized that my OnConnected() override method for my hub calls a method that tries to use the incoming principal with this.Context.User without checking if it's null. I removed that and anything else that tries to use the incoming principal in the hub, but unfortunately it still gives the same error.
I figured out what was wrong. It wasn't to do with the hub authentication, as I suspected as I was finishing this post. What was happening was my Startup file makes the call app.ConfigureAuth, but this eventually leads to some custom authorization in the OWIN middleware pipeline that checks if the incoming principal is null. I've taken that out for SignalR connections.
It should also be noted that after I got that working I couldn't make hub method calls from the proxy because it wasn't authorized to do so. To fix that I added
public override bool AuthorizeHubMethodInvocation(IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
to the HeadersAuthAttribute class, which I got from here.
I'm leaving old version of question on a bottom.
I'd like to implement custom authentication for SignalR clients. In my case this is java clients (Android). Not web browsers. There is no Forms authentication, there is no Windows authentication. Those are plain vanilla http clients using java library.
So, let's say client when connects to HUB passes custom header. I need to somehow authenticate user based on this header. Documentation here mentions that it is possible but doesn't give any details on how to implement it.
Here is my code from Android side:
hubConnection = new HubConnection("http://192.168.1.116/dbg", "", true, new NullLogger());
hubConnection.getHeaders().put("SRUserId", userId);
hubConnection.getHeaders().put("Authorization", userId);
final HubProxy hubProxy = hubConnection.createHubProxy("SignalRHub");
hubProxy.subscribe(this);
// Work with long polling connections only. Don't deal with server sockets and we
// don't have WebSockets installed
SignalRFuture<Void> awaitConnection = hubConnection.start(new LongPollingTransport(new NullLogger()));
try
{
awaitConnection.get();
Log.d(LOG_TAG, "------ CONNECTED to SignalR -- " + hubConnection.getConnectionId());
}
catch (Exception e)
{
LogData.e(LOG_TAG, e, LogData.Priority.High);
}
P.S. Original question below was my desire to "simplify" matter. Because I get access to headers in OnConnected callback. I thought there is easy way to drop connection right there..
Using Signal R with custom authentication mechanism. I simply check if connecting client has certain header passed in with connection request.
Question is - how do I DECLINE or NOT connect users who don't pass my check? Documentation here doesn't really explain such scenario. There is mentioning of using certificates/headers - but no samples on how to process it on server. I don't use Forms or windows authentication. My users - android java devices.
Here is code from my Hub where I want to reject connection..
public class SignalRHub : Hub
{
private const string UserIdHeader = "SRUserId";
private readonly static SignalRInMemoryUserMapping Connections = new SignalRInMemoryUserMapping();
public override Task OnConnected()
{
if (string.IsNullOrEmpty(Context.Headers[UserIdHeader]))
{
// TODO: Somehow make sure SignalR DOES NOT connect this user!
return Task.FromResult(0);
}
Connections.Add(Context.Headers[UserIdHeader], Context.ConnectionId);
Debug.WriteLine("Client {0}-{1} - {2}", Context.Headers[UserIdHeader], Context.ConnectionId, "CONNECTED");
return base.OnConnected();
}
So I just created a custom Authorization Attribute and overrode the AuthorizeHubConnection method to get access to the request and implemented the logic that you were trying to do with the Header and it appears to be working.
using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
namespace SignalR.Web.Authorization
{
public class HeadersAuthAttribute : AuthorizeAttribute
{
private const string UserIdHeader = "SRUserId";
public override bool AuthorizeHubConnection(HubDescriptor hubDescriptor, IRequest request)
{
if (string.IsNullOrEmpty(request.Headers[UserIdHeader]))
{
return false;
}
return true;
}
}
}
Hub
[HeadersAuth]
[HubName("messagingHub")]
public class MessagingHub : Hub
{
}
Which yields this in the console (if the picture doesn't show up, it's a [Failed to load resource: the server responded with a status of 401 (Unauthorized)]):
In fact, accepted answer is wrong. Authorization attribute, surprisingly, shall be used for authorization (that is, you should use it for checking whether requesting authenticated user is authorized to perform a desired action).
Also, since you using incorrect mechanics, you don't have HttpContext.Current.User.Identity set. So, you have no clear way to pass user info to your business / authorization logic.
And third, doing that you won't be able to use Clients.User() method to send message to specific user, since SignalR will be not able to map between users and connections.
The correct way is to plug in into OWIN authentication pipeline. Here is an excellent article explaining and demonstrating in detail how to implement custom authentication to be used in OWIN.
I not going to copy-paste it here, just follow it and make sure you implement all required parts:
Options
Handler
Middleware
After you have these, register them into OWIN:
app.Map("/signalr", map =>
{
map.UseYourCustomAuthentication();
var hubConfiguration = new HubConfiguration
{
Resolver = GlobalHost.DependencyResolver,
};
map.RunSignalR(hubConfiguration);
});
I have a small project which contains a windows forms signalr client and a windows forms server. Clients can connect to server. Now, I want to implement a login system. I read some blogposts, articles and questions about this, but I didn't found a way to do it. I would like to use the signalr authentication system so I can use attributes such as [Authorize] because it's already there.
To use this, I need to validate the username and password. Can the client sends the credentials in header like this
Connection = new HubConnection(BaseEngine.ServerURI);
Connection.Headers.Add("Username", username);
Connection.Headers.Add("Password", password);
HubProxy = Connection.CreateHubProxy("ChatHub");
await Connection.Start();
and the server should validate those credentials somehow in a method and throw an exception if are not valid?
I've tried to use the builtin system, but no luck. I couldn't obtain the Context.User in OnConnected method. As a workaround, I've tried to send the username and password in header and validate them, but the OnConnected method cannot throw errors to client. I am sure the client has to have an auth cookie, but I really don't know how to add it.
Thank you!
It is never a good idea to send password to the server this way , it is better to send a token that the server can validate .
also SignalR has some authentication features read more about this here
I have found something like a workaround: first of all, I implemented an attribute, derived from AuthorizeAttribute used by SignalR. This implementation overrides the AuthorizeHubMethodInvocation method witch is called when a method that is decorated with this attribute is called. So, in this method, I'm checking that an Authorization Token is present in the header of the request and validate the information. The client has to add this header to connect to the server. It's the easiest method I have found so far, but it's still an workaround.
Implementation, server:
[AttributeUsage(AttributeTargets.Method)]
internal class CustomAuthorizeAttribute : AuthorizeAttribute
{
public override bool AuthorizeHubMethodInvocation(Microsoft.AspNet.SignalR.Hubs.IHubIncomingInvokerContext hubIncomingInvokerContext, bool appliesToMethod)
{
string token = hubIncomingInvokerContext.Hub.Context.Headers["AuthenticationToken"];
if (string.IsNullOrEmpty(token))
return false;
else
{
string decryptedValue = Encryptor.Decrypt(token, Encryptor.Password);
string[] values = decryptedValue.Split(';');
string userName = values[0],
deviceId = values[1];
bool b = ...check if it's ok...
return b;
}
}
}
Implementation, client:
ComEngine.Connection = new HubConnection(BaseEngine.ServerURI);
ComEngine.Connection.Headers.Add("AuthenticationToken", Encryptor.Encrypt(string.Format("{0};{1};{2}", BaseEngine.UserName, BaseEngine.DeviceId, BaseEngine.Password), Encryptor.Password));
try
{
await Connection.Start();
}
catch (Exception ex)
{
...
}