SignalR 2.2.1 ASP.NET MVC 5 Edge/IE issue - c#

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.

Related

ASP.NET Core Angular - send different SignalR messages based on logged in user

I have Angular SPA ASP.NET Core app with Identity (IdentityServer4). I use SignalR to push real-time messages to clients.
However I have to "broadcast" messages. All clients receive same messages regardless of what they require and then they figure out in Typescript - do they need this message or not.
What I want is to be able to decide which SignalR client should receive message and what content - it will make messages shorter and cut out processing time on clients completely.
I see there is hub.Client.User(userId) method - thats what I need.. However it appears that the Identity user ID is not known to SignalR.
If I override public override Task OnConnectedAsync() - context inside doesnt have any useful information eg user/principals/claims - are empty.
How can I find out which IdentityServer4 user is connecting to the hub?
EDIT1 suggested implementing IUserIdProvider doesnt work - all xs are null.
https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0#use-claims-to-customize-identity-handling
public string GetUserId(HubConnectionContext connection)
{
var x1 = connection.User?.FindFirst(ClaimTypes.Email)?.Value;
var x2 = connection.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var x3 = connection.User?.FindFirst(ClaimTypes.Name)?.Value;
...
EDIT2 implemented "Identity Server JWT authentication" from https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0 - doesnt work either - accessToken is empty in PostConfigure
You need to implement IUserIdProvider and register it in the services collection.
Check this question - How to user IUserIdProvider in .NET Core?
There is an obvious solution to it. Here is the sample one can use after creating an asp.net core angular app with identity.
Note that in this particular scenario (Angular with ASP.NET Core with Identity) you do NOT need to implement anything else, in contrary to multiple suggestions from people mis-reading the doc: https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0
Client side:
import { AuthorizeService } from '../../api-authorization/authorize.service';
. . .
constructor(. . . , authsrv: AuthorizeService) {
this.hub = new HubConnectionBuilder()
.withUrl("/newshub", { accessTokenFactory: () => authsrv.getAccessToken().toPromise() })
.build();
Server side:
[Authorize]
public class NewsHub : Hub
{
public static readonly SortedDictionary<string, HubAuthItem> Connected = new SortedDictionary<string, HubAuthItem>();
public override Task OnConnectedAsync()
{
NewsHub.Connected.Add(Context.ConnectionId, new HubAuthItem
{
ConnectionId = Context.ConnectionId,
UserId = Context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value
});
return base.OnConnectedAsync();
}
}
Use it like this:
if(NewsHub.Connected.Count != 0)
foreach (var cnn in NewsHub.Connected.Values.Where(i => !string.IsNullOrEmpty(i.UserId)))
if(CanSendMessage(cnn.UserId)
hub.Clients.Users(cnn.UserId).SendAsync("servermessage", "message text");
It is transpired that User data is empty in SignalR server context because authorization doesnt work as I expected it to. To implement SignalR authorization with Identity Server seems to be a big deal and is a security risk as it will impact the whole app - you essentially need to manually override huge amount of code which already is done by Identity Server just to satisfy SignalR case.
So I came up with a workaround, see my answer to myself here:
SignalR authorization not working out of the box in asp.net core angular SPA with Identity Server
EDIT: I missed an obvious solution - see the other answer. This is still valid workaround though, so I am going to let it hang here.

Set-cookie not working for cross-site request / response in Dot net Core 3.1 & React setup same-site cookies and/or CORS issue

Alright, I am stumped! I have been trying to solve this for hours now with no luck. I am following this guide to use JWT for auth in a Dot Net Core 3.1 / React.js (typescript) project I am working on to learn the whole setup. I am working using cross site requests. My React server is communicating on https://localhost:3000 (dev using Visual Studio Code), and my API / back end, API server is running on https://localhost:44309 running in Visual Studio.
I am trying to send a refresh token back to the client and the guide states this needs to be in a HTTP Only cookie to mitigate XSS. No matter what I try, I cannot get the browser to execute the ‘set-cookie’ at the client side so I can see it in Google Chrome's Dev Tools > Application > Cookies. This is for any cookie that I set in the response at all. If I use Google Chrome’s Developer Tools panel, in the network response I can see the ‘set-cookie’ headers are there, but they never show in ‘Application > Cookies > LocalHost’. The response I am sending sends the payloads and that can be used / read with no issue. It just will not set cookies!
The setup works fine when I use the same server for client and server application parts (just run it in IIS in the standard Visual Studio setup); any / all cookies set with no problems, so I am guessing I am working with a cross-site issue. I just do not know how to fix it.
My code:
//setting up cors
services.AddCors(options =>
{
options.AddPolicy("CORSAllowLocalHost3000",
builder =>
builder.WithOrigins("https://localhost:3000")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials()
);
});
//using cors
app.UseCors("CORSAllowLocalHost3000");
//setting up auth
services
.AddDefaultIdentity<ApplicationUser>
(
options =>
{
options.SignIn.RequireConfirmedAccount = false;
options.Password.RequiredLength = 6;
}
)
.AddEntityFrameworkStores<IdentityApplicationContext>();
services
.AddAuthentication(opts =>
{
opts.DefaultAuthenticateScheme = "JwtBearer";
opts.DefaultScheme = "JwtBearer";
opts.DefaultChallengeScheme = "JwtBearer";
})
.AddJwtBearer("JwtBearer", opts =>
{
opts.SaveToken = true;
opts.TokenValidationParameters = tokenValParams;
});
//tokenValParams to validate the JWT
var tokenValParams = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key:
Encoding.ASCII.GetBytes(configuration.GetSection("Authentication").GetSection("JwtBearer").GetSection("SecurityKey").Value)),
ValidateIssuer = false,
ValidateAudience = false,
RequireExpirationTime = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
//for API only dev env, start as API only service - no browser - client app runs
app.UseStaticFiles();
if (!env.IsEnvironment("APIOnlyDevelopment"))
app.UseSpaStaticFiles();
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
//spa.UseProxyToSpaDevelopmentServer("https://localhost:3000");
});
//Test Cookie generated in client before async Ok() result is returned in controller
HttpContext.Response.Cookies.Append("JwtRefreshTokenn", resultOfSignin.RefreshToken, new CookieOptions() { Secure = true, HttpOnly = true, SameSite = SameSiteMode.None});
Response.Cookies.Append("JwtRefreshTokenn2", resultOfSignin.RefreshToken, new CookieOptions() { HttpOnly = true, Secure = true, SameSite = SameSiteMode.None});
Response.Cookies.Append("JwtRefreshTokenn3", resultOfSignin.RefreshToken, new CookieOptions() { });
Further information:
I have changed the ‘App URL’ in Visual Studio to that of the ‘Enable SSL’ URL as I was getting a CORS issue with a redirect that was occurring.
I am running the server using the inbuilt ‘HTTPS’ setup, and the client app using npm’s https setup
(including sorting the cert error out as with this post).
I have tried all combinations of cookie options, including adding domains / paths (and all variations of same-site attribute)
I have tried different things in the CORS policy (e.g. omitting .AllowCredentials)
I have tried using http rather than https
Firefox is still having CORS issues with the requests that Google Chrome is not
The problem is mirrored in MS Edge
All running in Windows 10
I am relatively new to this, so please let me know if I have missed anything out.
Any helps is greatly appreciated. Many thanks, Paul
After a good few hours invested in this, it was a simple fix that was required on the client. The setup above is / was good and working, with a particular mention to this:
services.AddCors(options =>
{
options.AddPolicy("CORSAllowLocalHost3000",
builder =>
builder.WithOrigins("https://localhost:3000")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials() // <<< this is required for cookies to be set on the client - sets the 'Access-Control-Allow-Credentials' to true
);
});
As the client is using axios to make API calls from within React, a global default was needed to be set to match this / work with this header. So at least in one place in the program where axios is imported, the default is set as so:
import axios from 'axios';
axios.defaults.withCredentials = true;
I had a custom axios creator file setup so that I could use e.g. interceptors etc, which is where I put this code. Once these two things were added & aligned, the cookies were being set.
Hope it helps.
I see the problem was with my front end. I was using the fetch api and there I needed to add credentials: "include" then the cookie was saved.

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!

Connect to signalR hub from c# web api

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);
});
}

C#: method to add WCF authentication, username + SSL?

I've written both a WCF client and a remote internet WCF server.
The remote WCF server is running WPF hosted in a traditional Windows Service wrapper (i.e. not IIS).
Currently, its working perfectly with basic HTTP binding. I'm using Visual Studio 2010 + .NET 4.0 + C#.
Can anyone point me in the direction of the right steps to alter my code so that I can add username + SSL authentication?
EDIT:
At the service end, I've overridden UserNamePasswordValidator as follows:
public class CustomUserNameValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
Console.WriteLine("Got to here");
}
}
At the service end, I've specified a link to the username validation class:
ServiceHost serviceHost = new ServiceHost(typeof(PhiFeedServer.PhiFeed)); // ,baseAddress);
const bool passswordAuthentication = true;
if (passswordAuthentication)
{
// These two lines switch on username/password authentication (see custom class "CustomUserNameValidator" in common file PhiFeed.svc.cs)
// See http://msdn.microsoft.com/en-us/library/aa354513.aspx
serviceHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
serviceHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new CustomUserNameValidator();
}
// Start the service
serviceHost.Open();
At the client end:
EndpointAddress endpointAddress = new EndpointAddress("http://localhost:20000/PhiFeed?wdsl");
BasicHttpBinding serviceBinding = new BasicHttpBinding();
serviceBinding.ReceiveTimeout = new TimeSpan(0, 0, 120);
proxy = new PhiFeedClient(serviceBinding, endpointAddress);
proxy.ClientCredentials.UserName.UserName = "myusername";
proxy.ClientCredentials.UserName.Password = "mypassword";
However, when I run everything, it never even calls the username validator - whats going on?
If i am getting this right, you will need to play around with service behaviour. I did that in 3.5 sp1 it should be the same in 4.0 i think.
read this:
http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/7d589542-277a-404e-ab46-222794422233/
Aha! Found the solution to my problem.
Microsoft provides example code which demonstrates how to add username/password + SSL authentication to a console app.
Search for "Windows Communication Foundation (WCF) and Windows Workflow Foundation (WF) Samples for .NET Framework 4", download, unzip into C:, then run the sample here:
C:\WF_WCF_Samples\WCF\Extensibility\Security\UserNamePasswordValidator\CS

Categories