I have a website using SignalR to connect to a status hub that announces changes to some remote servers to all the connected clients.
I'm self hosting SignalR using OWIN and have enabled CORS allow all on the server.
When I use http hosting everything is fine.
As soon as I move to https with certificates everything grinds to a halt.
XMLHttpRequest cannot load https://10.141.114.79:8081//signalr/negotiate?clientProtocol=1.4&connectionData=%5B%7B%22name%22%3A%22statushub%22%7D%5D&_=1411656116180. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://10.141.114.79:453' is therefore not allowed access.
Is there something I'm missing about SignalR and SSL which prevents CORS from working in Chrome?
Edit - Added Cors and MapSignalR
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR(new HubConfiguration { EnableJSONP = false, EnableDetailedErrors = true, EnableJavaScriptProxies = false });
Related
I've developed a simple WEB API service in .Net Core 2.1
I'm trying to implement a client certificate authentication, so I can give access to the APIs only to the clients that have a specific certificate installed on their machine.
The clients access the API using a browser (Chrome, Edge, IE11 or Firefox).
I've added in the API method the request for the certificate:
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
X509Certificate2 cert = Request.HttpContext.Connection.ClientCertificate;
if (cert!=null && cert.Verify())
{
//more verification here...
return Content("....", "application/json");
}
else
{
return Content("....", "application/json");
}
}
then I've installed a self-signed certificate and added to the Trusted Root, enabling the Client Authentication purpose.
but the variable cert is always null and the browser didn't even prompt me to use a certificate when I request the page.
I suppose because I have to set somewhere that the web server must ask for the client certificate as it is possible to set in IIS, but in my development environment, I'm using IIS Express.
How can I force IIS express to request a client certificate?
For proper certificate authentication using the ASP.NET Core authentication stack, you can also check out idunno.Authentication.Certificate by Barry Dorrans himself. It allows you to enable certificate authentication for your application and handles it like any other authentication scheme, so you can keep actual certificate-based logic out of your business logic.
This project sort of contains an implementation of Certificate Authentication for ASP.NET Core. Certificate authentication happens at the TLS level, long before it ever gets to ASP.NET Core, so, more accurately this is an authentication handler that validates the certificate and then gives you an event where you can resolve that certificate to a ClaimsPrincipal.
You must configure your host for certificate authentication, be it IIS, Kestrel, Azure Web Applications or whatever else you're using.
Make sure to also check out the “documentation” on how to set this up properly, since it requires configuration of the host to work properly, just like you did with IIS Express. Instructions for other servers like raw Kestrel, IIS, Azure or general reverse proxies are included.
In order to enable IIS Express to start requesting client certificates and therefore pass them to the server side, the configuration file must be edited:
The whole configuration is inside the solution folder in the .vs\config\applicationhost.config
Ensure the following values are set:
<security>
<access sslFlags="Ssl, SslNegotiateCert, SslRequireCert" />
and
<iisClientCertificateMappingAuthentication enabled="true"></iisClientCertificateMappingAuthentication>
For local testing, you can enable SSL in IIS Express from Visual Studio. In the Properties window, set SSL Enabled to True. Note the value of SSL URL; use this URL for testing HTTPS connections.
For Who needs
Details here
For .NET 3.1+ there is now official package supporting this feature:
builder.Services.AddAuthentication(
CertificateAuthenticationDefaults.AuthenticationScheme)
.AddCertificate(options =>
{
options.Events = new CertificateAuthenticationEvents
{
OnCertificateValidated = context =>
{
var validationService = context.HttpContext.RequestServices
.GetRequiredService<ICertificateValidationService>();
if (validationService.ValidateCertificate(context.ClientCertificate))
{
var claims = new[]
{
new Claim(
ClaimTypes.NameIdentifier,
context.ClientCertificate.Subject,
ClaimValueTypes.String, context.Options.ClaimsIssuer),
new Claim(
ClaimTypes.Name,
context.ClientCertificate.Subject,
ClaimValueTypes.String, context.Options.ClaimsIssuer)
};
context.Principal = new ClaimsPrincipal(
new ClaimsIdentity(claims, context.Scheme.Name));
context.Success();
}
return Task.CompletedTask;
}
};
});
There is also configuration required on the server side, see:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/certauth?view=aspnetcore-6.0
Using version 2.0 for Asp.NET SignalR, we have created a prototype application that has a WPF client application and a web site that has SignalR configured. This prototype works correctly when run on the local developer computer and when the web site was deployed to an internal development server.
An issue that has been encountered once the web site was deployed to an external server; the following exception is encountered when the HubConnection.Start method is called:
HttpClientException
A first chance exception of type 'Microsoft.AspNet.SignalR.Client.HttpClientException' occurred in mscorlib.dll
Additional information: StatusCode: 407, ReasonPhrase: 'Proxy Authentication Required ( Forefront TMG requires authorization to fulfill the request. Access to the Web Proxy filter is denied…
The network that the developer computer is on requires the use of a proxy to reach the Internet. The web site that has the SignalR component also has some WCF endpoints; these can be connected to using the HttpClient within the WPF client application when the proxy is set in code. The same approach to set the proxy was done on the HubConnection but the error is encountered.
Below is code on how the proxy is set to the HubConnection; the same credentials work when accessing the other, non-signalR, endpoints:
var proxyInfo = new WebProxy(new Uri(“theAddress”));
proxyInfo.Credentials = new NetworkCredential(“theUserName”, “thePassword”, “theDomain”);
hubConnection.Proxy = proxyInfo;
Is there something else that has to be set with the HubConnection for it to use the proxy?
Thanks,
Scott
The issue is that there is a bug with the 4.5 .NET Client for SignalR; the proxy information is not being sent with the requests in the HubConnection. This is a regression from the 1.0 release.
The link below contains the information:
https://github.com/SignalR/SignalR/issues/2856
I have a CORS problem when self-hosting SignalR with OWIN, which only happens when I try to enable authentication.
The error I get in my web browser is:
XMLHttpRequest cannot load http://.../signalr/negotiate?[snip] Origin ... is not allowed by Access-Control-Allow-Origin
This only happens if I enable authentication in my self-hosted server using the approach in this answer:
public void Configuration(IAppBuilder app)
{
var listener = (HttpListener)app.Properties[typeof(HttpListener).FullName];
listener.AuthenticationSchemes = AuthenticationSchemes.Ntlm;
app.MapHubs(new HubConfiguration { EnableCrossDomain = true });
}
If I comment out the AuthenticationSchemes line then CORS works (and I've checked everything in these instructions). I get the same problem if I use other authentication schemes than NTLM.
Using Fiddler to examine what's going on, without authentication enabled I see the necessary CORS headers coming back from the server:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: [my server]
However once I enable authentication I get a 401 response which is missing these headers. All the requests have the necessary Origin header.
Having examined the SignalR source code it looks like the headers are being set, but presumably with authentication enabled the HttpListener is sending the initial 401 response without hitting this code.
So I think my question is: How do I get the HttpListener to include an Access-Control-Allow-Origin header in its negotiation of authentication protocols?
I have gotten NTLM authentication to work with cross domain signalR self-hosted in OWIN by allowing the preflight requests anonymous access.
What one needs to do is create a delegate for choosing the authentication scheme which looks for the preflight request headers, and allows these through anonymously. All other requests will use NTLM.
public void Configuration(IAppBuilder appBuilder)
{
var listener = (HttpListener)appBuilder.Properties[typeof(HttpListener).FullName];
listener.AuthenticationSchemeSelectorDelegate += AuthenticationSchemeSelectorDelegate;
}
private AuthenticationSchemes AuthenticationSchemeSelectorDelegate(HttpListenerRequest httpRequest)
{
if (httpRequest.Headers.Get("Access-Control-Request-Method")!=null)
return AuthenticationSchemes.Anonymous;
else
return AuthenticationSchemes.Ntlm;
}
I presume you're using Chrome, which very unhelpfully tells you that these headers are missing and that this is the problem, when actually you have probably just forgot to set your XMLHttpRequest's withCredentials property to true.
If you're using jQuery you can do this for all requests with:
$.ajaxPrefilter(function (options, originalOptions, jqXHR) {
options.xhrFields = { withCredentials: true };
});
You also need to do the right thing with OPTIONS requests as in the other answer.
I'm trying to get a confirmation that yes, a client can send and receive messages to an ASP.NET site on another domain without requiring that the IIS server running the SignalR-enabled ASP.NET supports CORS.
Can someone provide me with an example I can look at where CORS is not used as the cross-domain mechanism?
We have IE 9 clients and want to have three sites on different domains push/pull to a single ASP.NET + SignalR server. Can this be done? How?
If cors is not available SignalR uses longPolling transport with jsonp.
Keep in mind jsonp is insecure by design AND can limit your data you send over the wire since all data is sent via the query string.
You should not have to provide any additional information on the client for SignalR to use jsonp, it should just work.
To ensure that cross domain communication works on the server you'll have to enable it:
Routes.RouteTable.MapHubs(new HubConfiguration { EnableCrossDomain = true });
To enable this when using SignalR 2.0 with the OWIN middleware instead of the regular ASP.NET pipeline, install the Microsoft.Owin.Cors package from NuGet, then do something like this:
[assembly: OwinStartupAttribute(typeof(OwinStartup))]
namespace Website.App_Start
{
public partial class OwinStartup
{
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
map.RunSignalR(new HubConfiguration {EnableJSONP = true});
}
}
}
}
More details here: http://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-javascript-client#crossdomain
Just to add, WebSockets (cross origin) will require CORS, so eventually (if you want to use web sockets) you will need to support this on the server.
This is driving me crazy.
I'm trying to access a WCF service via an ASP.NET web form,
I'm setting the binding for the channelfactory up like this:
BasicHttpBinding b = null;
if (Communication.SlServiceURL.StartsWith("https"))
b = new BasicHttpBinding(BasicHttpSecurityMode.Transport);
else
{
b = new BasicHttpBinding();
b.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
}
b.MaxBufferSize = 2147483647;
b.MaxReceivedMessageSize = 2147483647;
b.Security.Transport = new HttpTransportSecurity { ClientCredentialType = HttpClientCredentialType.Ntlm, ProxyCredentialType = HttpProxyCredentialType.None };
b.Security.Message = new BasicHttpMessageSecurity { ClientCredentialType = BasicHttpMessageCredentialType.UserName };
Then I create a new Channel Wrapper via:
public ClientChannelWrapper(Binding binding, EndpointAddress remoteAddress)
{
m_Factory = new ChannelFactory<T>(binding, remoteAddress);
}
I'm passing the binding and a https://[myservice]/myservice.svc URL as the remoteAddress.
The thing is: When I call the service in my production server with IIS 7, Windows Authentication enabled on the site and ONLY "NTLM" as the provider for the authentication, everything else disabled (no anonymous, forms, etc.)
I get the exception:
Exception type: MessageSecurityException
Exception message: The HTTP request is unauthorized with client authentication scheme 'Ntlm'. The authentication header received from the server was 'NTLM'.
This is pretty weird and I can't seem to find a way around this. I have also a Silverlight application hosted in another ASP.NET Form and there it works fine with this binding which is freaking me out because why wouldn't the same thing work in the ASP.NET site hosted in the same IIS site.
Also strange: When firing the site up from Visual Studio with F5 it works when accessing the production WCF service in the IIS7 machine. As soon as I deploy the site I get the exception.
Edit: It's clear now why Silverlight behaves different than ASP.NET, since Silverlight is communicating directly via the client computer with the service. Still no luck with the exception.