How to create credintals of SignalR client - asp.net-core

I try to create credintals or claims by different way to SignalR client, for example this way
connection = new HubConnectionBuilder().WithUrl(URL, opt => opt.Headers.Add("Bearer", myJWT)).
WithAutomaticReconnect(new[] { TimeSpan.FromSeconds(10) }).
Build();
But in my server hub I don't see anything in my Hub in this callback
public override Task OnConnectedAsync()
Connection established normally, I see it but in "Connect" object and "hubContext" object don't see anymore (user, headers, identity) its empty.
I have no special security configuration of my SignalR service, I have configure only SignalR endpoint, but I would expect to see at least client headers in my server hub, but no.

You can get that in OnConnectedAsync method by using this property:
Context.GetHttpContext().Request
And just a heads up, you want to do authorization header like this:
opt.Headers.Add("Authorization", myJWT)
Then grab it like this:
var token = Context.GetHttpContext().Request.Headers["Authorization"];

Related

Azure SignalR Serverless OnConnected-trigger with Azure-functions (Traditional model)

I am using the Azure SignalR Service in combination with Azure Functions and I have the following code:
public class SignalRHubFunction
{
[FunctionName("SignalRConnected")]
public async Task Run([SignalRTrigger("myhubname", "connections", "connected", ConnectionStringSetting = "AzureSignalRConnectionString")] InvocationContext invocationContext, ILogger logger)
{
logger.LogInformation($"{invocationContext.ConnectionId} connected");
}
}
I have a hard time getting a trigger on the 'OnConnected' event. Samples are only given with Class based model.
And the docs aren't really helpful for me.
Docs are telling me the category parameter of the SignalRTrigger constructor should be: connections or messages.
So I use connections.
This value must be set as the category of messages for the function to be triggered. The category can be one of the following values:
connections: Including connected and disconnected events
messages: Including all other events except those in connections category
I don't really understand what the docs mean with the event parameter
This value must be set as the event of messages for the function to be triggered. For messages category, event is the target in invocation message that clients send. For connections category, only connected and disconnected is used.
I guess they are saying you can choose between connected and disconnected.
However with the code from above the trigger is never hit. Any thoughts?
Original Answer: https://learn.microsoft.com/en-us/answers/questions/159266/debug-function-using-a-signalrtrigger.html
For the SignalRTrigger to work, you need to set a webhook to the function in SignalR.
When you deploy a function with the SignalRTrigger, it does 2 extra things:
Create the webhook: https://<APP_NAME>.azurewebsites.net/runtime/webhooks/signalr
Create an API key code (signalr_extension) in the function app system settings (see "system keys" section in "App Keys" blade of the azure portal Function App)
SignalR POSTs events to this webhook (so obviously this doesn't work on local, unless you have a publicly addressable IP that you can add to SignalR).
To configure the SignalR event webhook, go to "Settings" blade of SignalR and add the upstream URL https://<APP_NAME>.azurewebsites.net/runtime/webhooks/signalr?code=<API_KEY>
Voila! This should now work
Ref: https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-signalr-service-trigger?tabs=javascript#send-messages-to-signalr-service-trigger-binding
This code snippet works.
[FunctionName("OnConnected ")]
public async Task OnConnected ([SignalRTrigger("YourHub", "connections", "connected ")]
InvocationContext invocationContext, ILogger logger)
{
logger.LogInformation($"{invocationContext.ConnectionId} has connected");
}
Also configure, Upstream URL in Azure SignalR settings
<Function_App_URL>/runtime/webhooks/signalr?code=<API_KEY>
SignalR Service integration
The Function_App_URL can be found on Function App's Overview page and The API_KEY is generated by Azure Function. You can get the API_KEY from signalr_extension in the App keys blade of Function App.

protobuf-net.grpc client and .NET Core's gRPC client factory integration

I am experimenting with a gRPC service and client using proto files. The advice is to use gRPC client factory integration in .NET Core (https://learn.microsoft.com/en-us/aspnet/core/grpc/clientfactory?view=aspnetcore-3.1). To do this you register the client derived from Grpc.Core.ClientBase that is generated by the Grpc.Tools package, like this:
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddGrpcClient<MyGrpcClientType>(o =>
{
o.Address = new Uri("https://localhost:5001");
});
})
My understanding is that MyGrpcClientType is registered with DI as a transient client, meaning a new one is created each time it is injected, but that the client is integrated with the HttpClientFactory, allowing the channel to be reused rather than be created each time.
Now, I would like to use protobuf-net.grpc to generate the client from an interface, which appears to be done like this:
GrpcClientFactory.AllowUnencryptedHttp2 = true;
using var http = GrpcChannel.ForAddress("http://localhost:10042");
var calculator = http.CreateGrpcService<ICalculator>();
If I am correct in thinking that channels are expensive to create, but clients are cheap, how do I achieve integration with the HttpClientFactory (and so reuse of the underlying channel) using protobuf-net.grpc? The above appears to create a GrpcChannel each time I want a client, so what is the correct approach to reusing channels?
Similarly, is it possible to register the protobuf-net.grpc generated service class with the below code in ASP.Net Core?
endpoints.MapGrpcService<MyGrpcServiceType>();
(Please correct any misunderstandings in the above)
Note that you don't need the AllowUnencryptedHttp2 - that's just if you aren't using https, but: you seem to be using https.
On the "similarly"; that should already work - the only bit you might be missing is the call to services.AddCodeFirstGrpc() (usually in Startup.cs, via ConfigureServices).
As for the AddGrpcClient; I would have to investigate. That isn't something that I've explored in the integrations so far. It might be a new piece is needed.
The Client Factory support not exists, and works exactly like documented here except you register with the method
services.AddCodeFirstGrpcClient<IMyService>(o =>
{
o.Address = new Uri("...etc...");
});

UserId in SignalR Core

I'm using SignalR with ASP.NET Core 2.0 and I'm trying to send a notification for a specific user like this:
_notification.Clients.User(id).InvokeAsync("SendMes");
where _notification is IHubContext.
But it doesn't work. When I send the notification for all users, everything is fine and all users get the notification. But when I send it to a specific user, nothing happens. In connections I have needed user but it seems as if he doesn't have userId. So how can I do this? By access to Identity and claims? If so, how to do this?
I was facing a similar problem and the following article helped me figure it out: https://learn.microsoft.com/en-us/aspnet/core/signalr/groups?view=aspnetcore-2.1
In the Hub (server-side), I checked Context.UserIdentifier and it was null, even though the user was authenticated. It turns out that SignalR relies on ClaimTypes.NameIdentifier and I was only setting ClaimTypes.Name. So, basically I added another claim and it worked out (Context.UserIdentifier is set correctly after that).
Below, I share part of the authentication code I have, just in case it helps:
var claims = userRoles.Split(',', ';').Select(p => new Claim(ClaimTypes.Role, p.Trim())).ToList();
claims.Insert(0, new Claim(ClaimTypes.Name, userName));
claims.Insert(1, new Claim(ClaimTypes.NameIdentifier, userName)); // this is the claim type that is used by SignalR
var userIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
Be careful, SignalR users and groups are case-sensitive.
As an addendum to the answer #Chris provided, the DefaultUserIdProvider implementation for IUserIdProvider is actually added using the TryAdd*() method.
services.TryAddSingleton(typeof(IUserIdProvider), typeof(DefaultUserIdProvider));
So all you have to do is add your own custom implementation of IUserIdProvider before calling services.AddSignalR(), and SignalR will skip adding its own when it sees yours.
services.AddSingleton<Microsoft.AspNetCore.SignalR.IUserIdProvider, MyCustomUserIdProvider>();
services.AddSignalR();
The problem was that I created my ClaimsIdentity object used with cookie like this:
new ClaimsIdentity(claims, "ApplicationCookie", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
Where DefaultNameClaimType was my email. When I changed 'ClaimsIdentity.DefaultNameClaimType' to 'ClaimsTypes.NameIdentifier' which is my user id, all worked correctly with this code:
_notification.Clients.User(id).InvokeAsync("SendMes");
If you use JWT, you have to add the NameIdentifier Claim in the SecurityTokenDescriptor:
new Claim(ClaimTypes.NameIdentifier, userId)
The user id provider defaults to using IPrincipal.Identity.Name, which for most Identity deployments, ends up being the email address. In older SignalR, this could be customized by using your own provider.
You would simply implement the following interface:
public interface IUserIdProvider
{
string GetUserId(IRequest request);
}
And then attach it via:
GlobalHost.DependencyResolver.Register(typeof(IUserIdProvider), () => new MyIdProvider());
I'm not sure how much of that has changed in the Core version. IUserIdProvider still exists, though the interface has changed slightly:
public interface IUserIdProvider
{
string GetUserId(HubConnectionContext connection);
}
When you call AddSignalR in ConfigureServices, it sets up the following, among other things of course:
services.AddSingleton(typeof(IUserIdProvider), typeof(DefaultUserIdProvider));
The DefaultUserIdProvider is obviously the default implementation. There doesn't seem to be any configuration option to override this, so if you need to use your own provider, you'll have to replace the service descriptor:
services.Replace(ServiceDescriptor.Singleton(typeof(IUserIdProvider),
typeof(MyCustomUserIdProvider)));
Obviously, that would need to come after the call to AddSignalR. Also, be aware that you must configure auth first, before configuring SignalR. Otherwise, the user will not be available to the hub.

WCF sessions are not specific to specific user

WCF function
public void SetSession(string name)
{
HttpContext.Current.Session["abc"]=name;
}
public string GetSession(string name)
{
return HttpContext.Current.Session["abc"].ToString();
}
Proxy
using (ServiceReference1.BlackjackClient proxy = new ServiceReference1.BlackjackClient())
{
proxy.SetSession("Hello");
}
my problem is when multiple clients are accessing the service then last set session is accessed by the each client. Session are not browser request based and not recognizing the client. Which client has sent which request. What should i do to make them specific to each client. means each client must have his own session.
Please help
The service can not know which client is calling the service. Regular asp.net use of Session uses a cookie, that identifies each request and makes some internal voodoo to map the request to the correct session.
In your case, you would have to either use login from the clients to ensure that the service could identify requests, but this would not in it self solve the problem.
Since you have access to the service implementation the simplest solution would probably be to store a session identifier (a Guid) in the client, and then send this along each request to the web service thus altering
public void SetSession(string name)
{
HttpContext.Current.Session["abc"]=name;
}
public string GetSession(string name)
{
return HttpContext.Current.Session["abc"].ToString();
}
to something like
public void SetSession(string name, Guid sessionId)
{
HttpContext.Current.Session[sessionId + "_abc"]=name;
}
public string GetSession(string name, Guid sessionId)
{
return HttpContext.Current.Session[sessionId + "_abc"].ToString();
}
Modifying the method signature like this is rather ugly though, but the idea would be, that the client aids the server in identifying the caller and thus the session.
It would be cleaner to use the transport protocol to identify the caller, so if you are creating a HTTP service, you could use some http header (perhaps authorization) to contain the session identifier. If you are using SOAP the message header could contain identical information.
The session identifier could also be created at the service by a new method named something like Guid CreateSession(). But a Guid could as well be created in the client.
But again: You will need to store some unique session id or user credentials in the client and communicate them to the server in each request.

Creating a WCF Web Api Client

I've been using the WCF Web Api recently and i've been using the WCF Web API Test Client that is built in to it to test my created webservices.
I am wanting to create a proxy in code built off of the interface rather than run svcutil.exe to create a proxy.
My webservice is working fine, however when I use fiddler to examine the message that is sent, it is putting in a namespace into the xml message.
Below is the code I use to send the request.
RegisterRequest registerRequest = new RegisterRequest
{
Email = "test#test.com",
Firstname = "firstname",
Lastname = "lastname",
Password = "password"
};
var factory = new ChannelFactory<IAccountApi>(new WebHttpBinding(), "http://localhost/WebServices/api/account");
factory.Endpoint.Behaviors.Add(new WebHttpBehavior());
var proxy = factory.CreateChannel();
proxy.Register(registerRequest);
This request below is generated via the client, and it fails, returning a 500 internal server error
<RegisterRequest xmlns="http://schemas.datacontract.org/2004/07/ServiceModel.Accounts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"><Email>test#test.com</Email><Firstname>firstname</Firstname><Lastname>lastname</Lastname><Password>password</Password></RegisterRequest>
Using the same headers when I send using the api test client the following message passes
<RegisterRequest><Email>test#test.com</Email><Firstname>firstname</Firstname><Lastname>lastname</Lastname><Password>password</Password></RegisterRequest>
The only difference being the namespace has been removed.
Some final points,
1) If I were able to remove this namespace the request would work
2) I am not sure if ChannelFactory can be used in conjunction with WCF Web Api. The reason being http://wcf.codeplex.com/releases/view/73423 states "[ServiceContract] is no longer required on the Web API class definition", yet Channel Factory requires it.
3) All the examples so far from the WCF Web API look like the following
HttpClient client = new HttpClient();
Contact contact = new Contact() { Name = name };
var response = client.Post("http://localhost:9000/api/contacts/",
new ObjectContent<Contact>(
contact, JsonMediaTypeFormatter.DefaultMediaType));
Should I be using HttpClient for my requests instead of channel factory?
Regards,
Andrew
It appears that the IAccountApi, which you do not show, is defining a namespace for the service contract. If you really want an empty namespace (not best practice) try something like this:
[ServiceContract(Namespace="")]
public interface IAccountApi
{ ... }
If the namespace is not defined for IAccountApi, check the [DataContract] of RegisterRequest.
I ended up using HttpClient class, it allows GET, POST, PUT and DELETE which was fine for WCF Web API (now called http://www.asp.net/web-api)
http://msdn.microsoft.com/en-us/library/system.net.http.httpclient.aspx
as far as building a rest proxy using codegen or being dynamic see this article
ReST Proxy Object Generator