Connect to signalR hub from c# web api - c#

I've been tasked with trying to move our signalR hub to an azure cloud service with a service bus backplane. No problems there. The javascript client is able to get the hubs.js and connect without errors. We also have a web api project that needs to send messages to the hub but I cannot get it to connect. Everything I've tried doesn't work and the connection times out. I must be missing something but, since this is my first time working with signalR and Azure, I don't know what it is. The web api is hosted on IIS.
Here is the code I am trying to use to connect:
private async void InitializeHub()
{
string connectionString = "http://xxxx-xxxxx.cloudapp.net/signalr";
var hubConnection = new HubConnection(connectionString, useDefaultUrl: false);
//var hubConnection = new HubConnection(connectionString);
HubProxy = hubConnection.CreateHubProxy("clientPortalHub");
await hubConnection.Start();
}
Is there some configuration I am missing? Anything need to be done in IIS? I'm not getting any helpful error messages, just that the connection times out. I can hit the url (the real one, not the one I pasted) in a browser and get "Unknown transport".
If it helps here is the startup from the hub:
public void Configuration(IAppBuilder app)
{
// Any connection or hub wire up and configuration should go here
string connectionString = "<omitted>";
GlobalHost.DependencyResolver.UseServiceBus(connectionString, "clientPortalHub");
app.Map("/signalr", map =>
{
map. UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
// You can enable JSONP by uncommenting line below.
// JSONP requests are insecure but some older browsers (and some
// versions of IE) require JSONP to work cross domain
// EnableJSONP = true
};
hubConfiguration.EnableDetailedErrors = true;
map.RunSignalR(hubConfiguration);
});
}

Related

.Net SignalR use JWT Bearer Authentication when Cookie Authentication is also configured

I have a ASP.NET 5 WebApp that is part of a bigger system and uses Cookie Authentication for Browser requests.
I want to add the ability to request data and perform specific actions on certain Windows services that are also part of the overall system and are executed on a couple of seperate PCs. I want to use SignalR for this.
Then Windows-Services are running as a dedicated service identity that is part of our ActiveDirectory. Since the services shall not store their user credentials in code or local configuration files, they are requesting an authentication token for the web application from an API that works with Windows Authentication.
Then, when establishing the SignalR connection with the web app, the services will use the token received from the API to authenticate against the web app. This is working in general.
The Authentication configuration of the web app is:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = "/Login";
options.ExpireTimeSpan = TimeSpan.FromMinutes(12);
options.SlidingExpiration = true;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, opt =>
{
// Configuration details excluded
// ...
opt.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// ...
}
};
According to Microsoft Documentation this should be a vaild authentication configuration.
In services.AddAuthorization(...) method I've added a policy specific for Bearer scheme:
options.AddPolicy("SignalRService", policy =>
{
policy.RequireRole("SignalRService");
policy.AuthenticationSchemes.Add(JwtBearerDefaults.AuthenticationScheme);
});
And then there is a SignalR Hub Method secured with this policy:
[Authorize(Policy = "SignalRService")]
public async Task RegisterService(string clientIdString) { /*...*/ }
And finally the hub connection in the windows service is created as follows:
connection = new HubConnectionBuilder()
.WithUrl(hubAddress, options =>
{
options.AccessTokenProvider = () => Task.FromResult(authToken);
})
.WithAutomaticReconnect()
.Build();
Establishing the connection works:
await connection.StartAsync();
But when I try to call the hub method from the windows service like await connection.InvokeAsync("RegisterService", clientId); I receive a HubException with the message:
Failed to invoke 'RegisterService' because user is unauthorized
I have also created an API Controller on the web app for testing purposes and secured it with the same policy:
[HttpGet]
[Authorize(Policy = "SignalRService")]
public IActionResult Get()
{
return Ok(User.Identity.Name);
}
When I call this API endpoint with the same token i would user for SignalR Hub call, I get the identity set on the token returned as expected. I also verified that the configured OnMessageReceived event handler is executed in this scenario, while it isn't when I use SignalR connection.
When I set JwtBearerDefaults.AuthenticationScheme as the default scheme in Startup.cs instead of CookieAuthenticationDefaults.AuthenticationScheme it works also with the SignalR Hub, but then my standard Cookie based user authenticaton breaks.
I expect that there is some additonal configuration necessary to tell the web app to explicitely use the Bearer scheme when a Hub method is called, but I could not find anything so far.
After desperately trying for another hour, I found out that the specific bearer authentication worked with Cookie authentication as the default, when I put the Authorize(Policy = "SignalRService") directly on the class instead of on the method.
Since my hub should also be accessible for browser connections using cookies, I finally ended up with:
[Authorize(AuthenticationSchemes = "Bearer,Cookies")]
public class SignalRServiceHub : Hub
{
[Authorize(Policy = "SignalRService")]
public async Task RegisterService(string clientIdString)
{
// ...
}
[Authorize(Policy = "Root")]
public async Task RegisterMonitoringClient()
{
// ...
}
I'm not exactly sure why specifying the Schemes on class level is necessary in this case while it isn't for ApiController implementations

Accessing WCF application through Service Fabric reverse proxy

We've made a a WCF application that we're hosting inside an On-Premise Service fabric cluster. Accessing it through the Service Fabric reverse proxy is giving us some difficulties.
Our cluster has 3 nodes(eg. 10.0.0.1-3) and the application should be accessible through the reverse proxy (listening on port 19081) on every node. Unfortunatly it only works through the SF reverse proxy on the node hosting the WCF application(also listening on port 19081). Accessing it through the other nodes results in a 400 bad request.
If we run the WCF service on a different port, we can access it directly / locally, but not through the Service Fabric Reverse Proxy.
We're running multiple ASP.NET Core/REST services on the cluster and these work fine.
Example
If the service is running on the 10.0.0.1 node we can access it through:
http://10.0.0.1:19081/myserviceType/soaphost/myservice.svc
However these URL's result in a 400 bad request status code:
http://10.0.0.2:19081/myserviceType/soaphost/myservice.svc
http://10.0.0.3:19081/myserviceType/soaphost/myservice.svc
Code example
We're using the following code to create the WCF Service Instance Listener:
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners()
{
return new ServiceInstanceListener[] {
CreateWcfListener("ServiceEndpoint", serviceProvider.GetService<ServiceType>())
};
}
private ServiceInstanceListener CreateWcfListener<T>(string endpoint, T serviceImplementation)
{
return new ServiceInstanceListener((context) =>
{
var endpointConfig = context.CodePackageActivationContext.GetEndpoint(endpoint);
int port = endpointConfig.Port;
string scheme = endpointConfig.Protocol.ToString();
string host = context.NodeContext.IPAddressOrFQDN;
string uriStem = endpointConfig.PathSuffix;
string uri = $"{scheme}://{host}:19081{context.ServiceName.AbsolutePath}/{uriStem}";
CustomBinding listenerBinding = CreateListenerBinding();
WcfCommunicationListener<T> listener = new WcfCommunicationListener<T>(
wcfServiceObject: serviceImplementation,
serviceContext: context,
address: new EndpointAddress(uri),
listenerBinding: listenerBinding);
return listener;
}, endpoint);
}
We would like to know why it doesn't work, but more importantly how to fix it.

IdentityServer 4, trying to capture traffic with fiddler?

Console application trying to get discovery
var disco = await DiscoveryClient.GetAsync("http://localhost:5000");
Works fine, however i'm trying to figure out how this thing works and I cant seem to capture the http traffic.
if i use http://localhost.fiddler to redirect to the local proxy Errors With:
Error connecting to localhost.fiddler:5000/.well-known/openid-configuration: HTTPS required (it's not setup with HTTPS, the error msg is misleading!)
Strangely later in the code when we try to authenticate to web-api with
var response = await client.GetAsync("http://localhost.fiddler:5001/identity");
localhost.fiddler works fine, now this is running in the same console.app, in program.cs so the same file. This is driving me potty why on earth can't I capture traffic going to 5000 it's HTTP!!! so what mysteries are causing this ? is there another way to view the magic http traffic going to and from Identity Server ?
Added Startup class
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// configure identity server with in-memory stores, keys, clients and scopes
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())
.AddTestUsers(Config.GetUsers());
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseIdentityServer();
}
}
added Blog, will update it and credit if we can resolve this.
As you correctly figured out, you need to use, for example, http://localhost.fiddler, to route localhost traffic through fiddler. However, using DiscoveryClient.GetAsync uses DiscoveryClient with default policy. That default policy has the following settings important for this case:
RequireHttps = true
AllowHttpOnLoopback = true
So, it requires https unless you query loopback address. How it knows what is loopback address? There is DiscoveryPolicy.LoopbackAddresses property. By default it contains:
"localhost"
"127.0.0.1"
For that reason you have "HTTPS required" error - "localhost.fiddler" is not considered a loopback address, and default policy requires https for non-loopback addresses.
So to fix, you need to either set RequireHttps to false, or add "localhost.fiddler` to loopback address list:
var discoClient = new DiscoveryClient("http://localhost.fiddler:5000");
discoClient.Policy.LoopbackAddresses.Add("localhost.fiddler");
//discoClient.Policy.RequireHttps = false;
var disco = await discoClient.GetAsync();
If you do this - you will see disovery request in fiddler, however it will fail (response will contain error), because server will report authority as "http://localhost:5000" and you query "http://localhost.fiddler:5000". So you also need to override authority in your policy:
var discoClient = new DiscoveryClient("http://localhost.fiddler:5000");
discoClient.Policy.LoopbackAddresses.Add("localhost.fiddler");
discoClient.Policy.Authority = "http://localhost:5000";
var disco = await discoClient.GetAsync();
Now it will work as expected.

How can I connect to my SignalR hub from a different host?

I have an ASP.NET MVC 5 app that is running SignalR 2.2.2 this app is running on hub.domain.com. On another Asp.Net MVC-5-based app (i.e. localhost:15371) I want to interact with the hub.
On my localhost:15371 application, I added the following code
<script>
var myHubHost = 'http://hub.domain.com/';
</script>
<script src="http://hub.domain.com/Scripts/jquery.signalR-2.2.2.min.js"></script>
<script src="http://hub.domain.com/signalr/hubs"></script>
<script src="http://hub.domain.com/signalr/custom_code.js"></script>
However, I am getting the following error when trying to connect to the hub on app.domain.com but it is working fine when I run it directly from hub.domain.com
Error: Error during negotiation request.
To enable CORS on my hub app by adding the following to my <system.webServer></system.webServer> section in the Web.config file.
<httpProtocol>
<customHeaders>
<add name="Access-Control-Allow-Origin" value="*" />
</customHeaders>
</httpProtocol>
I also, tried to enable JSONP and and the detailed errors on my hub like so
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
var hubConfiguration = new HubConfiguration();
hubConfiguration.EnableDetailedErrors = true;
hubConfiguration.EnableJSONP = true;
app.MapSignalR(hubConfiguration);
}
What could be causing this error? What else needs to be done to connect to my hub from another app?
The code that is used to connect to the my hub is as follow and found in the custom_code.js file.
$(document).ready(function () {
// Reference the auto-generated proxy for the hub.
var app = $.connection.myHubName;
// The getApiUrl() method return http://hub.domain.com/signalr
// as the myHubHost variable is set to http://hub.domain.com/
$.connection.hub.url = getApiUrl('signalr');
$.connection.hub.error(function (error) {
console.log(error)
});
// Create a function that the hub can call back get the new events
app.client.updatePanel = function (message) {
// Do stuff
}
// Start the connection.
$.connection.hub.start();
// This allows me to set a variable to control the base-url host when including this file on a different app.
function getApiUrl(uri)
{
var link = "/";
if (typeof window.myHubHost !== typeof someUndefinedVariableName) {
link = window.myHubHost;
}
return link + uri;
}
});
UPDATED
I enabled logging as per like so
$.connection.hub.logging = true;
I also installed Microsoft.Owin.Cors package to enable cors as per the documentation. Here is my current configuration
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
EnableDetailedErrors = true
};
map.RunSignalR(hubConfiguration);
});
Here is the logs that I get in the console. As you can see, the negotiation fails.
SignalR: Auto detected cross domain url.
SignalR: Client subscribed to hub 'myHubName'.
SignalR: Negotiating with 'http://hub.domain.com/signalr/negotiate?clientProtocol=1.5&connectionData=%5B%5D'.
SignalR error: Error: Error during negotiation request.
SignalR: Stopping connection.
I figured out my problem finally.
I had html base tag in my layout which was causing the problem.
I removed
and my problem was solved!

SignalR 2.2.1 ASP.NET MVC 5 Edge/IE issue

I have a problem with Signal r 2.2.1 ASP.NET MVC 5. On Chrome the SignalR server works great(i can communicate with the server), but on Edge or IE he doesn't work.
The Signal R server is an ASP.NET MVC 5 Project.
Microsoft Edge Console Logging:
Chrome:
Server startup code:
public class StartupConfiguration : Controller
{
public void Configuration(IAppBuilder app)
{
GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110);
GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(60);
// This value must be no more than 1/3 of the DisconnectTimeout value.
GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(20);
var hubConfiguration = new HubConfiguration
{
EnableDetailedErrors = true,
EnableJSONP = false,
};
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR("/signalr", hubConfiguration);
}
}
Note: Same code as Class Library (Windows Service) works great on Edge/IE/Chrome, but as Web Application (ASP.NET MVC5) it doesn't work.
Maybe I'm missing something?
Can someone help me with this issue?
It's fixed by enabling the WebSocket on the server side. Microsoft Edge and IE doesn't support Server Sent Events (SSE) so the client falls back to long polling.

Categories