Detecting SignalR authorize failures from web client - signalr

I'm experimenting with SignalR hosted in an asp.net MVC5 application and am having an issue detecting authorization failures from a web client.
I have a simple hub as follows:
public class ChitChat : Hub
{
[Authorize]
public string Hi(string incoming)
{
Clients.All.echo(incoming);
return "Echoed " + incoming;
}
}
And on the web page:
$(function() {
var hub = $.connection.chitChat;
hub.client.echo = function(msg) {
console.log(msg);
}
$.connection.hub.start().done(function() {
console.log("Done starting hub");
hub.server.hi("Client message")
.done(function() {
console.log(arguments);
})
.fail(function() {
console.log(arguments);
});
})
.fail(function() {
console.log("Fail hub" + arguments);
});
});
When I enable detailed errors in the hub configuration, I get this in the promise rejection for hi
Error: Caller is not authorized to invoke the Hi method on ChitChat.
Without detailed errors, I just get
Error: There was an error invoking Hub method 'chitchat.Hi'.
I'd like to keep detailed errors off, but still get some dependable way of identifying auth failures (I'd assumed I would get a 401/403 code somewhere). Is there a way to achieve this?

SignalR uses jQuery under the hood, so detecting the 401 status code could be achieved by following:
$.ajaxSetup({
statusCode: {
401: function() {
// your code is here
}
}
});

Given a rejection r, check r.source.status. This should give you a 401/403 when it is an authorization problem.

Related

Signal connecting to hub but can't invoke methods

The problem
We are experiencing some weird problems with our signalR service. The signalR service sometimes works, and sometimes it doesn't. The service connects without issues, but when we try to invoke some of the methods i get the following error:
An unexpected error occurred invoking XXXX on the server.
We have tried enabling detailed logging, but the error message was the same. In the snippet below you can see the error we are getting, and you can furthermore see that the client keeps pinging the server without problems after the error.
SignalR ws image
What we have tried
We have 3 different environments set up with signalR. One of the environments is working but we are experiencing the above mentioned error in our 2 other environments. We tried checking the code on the working environment down on one of those that didn't work, and that didn't fix our signalR problem. What we tried so far:
Restarting the signalR service.
Renaming the hubs.
Cleaning and rebuilding the solution.
The signalR logs doesn't tell us much too. This is the message we are getting out of the logs on Azure:
"message":"Connection aborted. Reason: Connection ping timeout.","type":"ConnectivityLogs","collection":"Connection"
This is the method that fails:
public async Task AddToItemGroupTest(Guid itemId)
{
var securityLogger = await CreateSecurityLoggerForConnectionChanges(itemId);
await Groups.AddToGroupAsync(Context.ConnectionId, GetItemGroupIdentifier(itemId, _applicationSettings.EnvironmentShortName));
securityLogger.LogDataCreate($"connection to itemhub on item with id: {itemId}");
}
Client side:
NotificationService.getItemConnection().invoke("AddToItemGroup" + Constants.environmentShortName, itemId)
.then(() => {
this.addGroupToActiveGroups("item", "AddToItemGroup" + Constants.environmentShortName, itemId);
connectionChangedCallback(connectionStatus.connected);
callback();
})
.catch((error) => {
connectionChangedCallback(connectionStatus.disconnected);
console.log(`Failed adding client to group. Error: ${error}`);
throw Error(`Failed adding client to group. Error: ${error}`);
});
getItemConnection looks like this:
private static getItemConnection() {
if (!this.itemConnection) {
this.itemConnection = new SignalR.HubConnectionBuilder()
.withUrl(Constants.itemHubUrl, {
accessTokenFactory: () => {
return getUserAccesTokenOrForceLogin(this.authService);
}})
.withAutomaticReconnect()
.build();
}
return this.itemConnection;
}

Incorrect path to SignalR generated on client

So I have been plugging away trying to get SignalR to work with my Web Api instance that used OWIN and authentication. After figuring out CORS (thanks to SO help) I am almost there.
My web client fails with a Javascript error on the console.
http://localhost:45325/negotiate?clientProtocol=1.5&connectionData=%5B%7B%22name%22%3A%22myhub%22%7D%5D&_=1460577212205 404 (Not Found)
if I take this URL and put it in the browser sure enough I get a 404 error. However if I add /signalr/ to the path as such ...
http://localhost:45325/signalr/negotiate?clientProtocol=1.5&connectionData=%5B%7B%22name%22%3A%22myhub%22%7D%5D&_=1460577212205
I get a proper JSON response with the SignalR connectionid and everything ...
{
"Url": "/signalr",
"ConnectionToken": "<token here>",
"ConnectionId": "0bf84c7a-0a28-4da9-bb9f-551de894cf0e",
"KeepAliveTimeout": 20,
"DisconnectTimeout": 30,
"ConnectionTimeout": 110,
"TryWebSockets": true,
"ProtocolVersion": "1.5",
"TransportConnectTimeout": 5,
"LongPollDelay": 0
}
So it looks as though everything is working other than the fact that the URL the client is generating to connect to the SignalR hub is missing the /signalr.
Here is my client side Javascript that connects to the Hub. Where can I specify the path needs to include /signalr? Because I thought I already was ...
<script src="scripts/jquery-2.2.2.min.js"></script>
<script src="scripts/jquery.signalR-2.2.0.js"></script>
<script>
(function ($) {
$(function () {
var connection = $.hubConnection('/signalr/', {useDefaultPath: false});
var myHubProxy = connection.createHubProxy('MyHub');
myHubProxy.on('notify', function (username) {
console.log(username + ' has logged in');
alert(username + ' has logged in');
});
connection.url = 'http://localhost:45325';
connection.start()
.done(function() {
alert('Connected to MyHub: Connection ID = ' + connection.id);
})
.fail(function(e) {
console.log('Connection error: ' + e);
});
});
})(jQuery);
</script>
You may notice that I did not include <script src="signalr/hubs"></script>. This is because I am created the proxy myself instead of relying on the auto-generated proxy
I figured it out! It should have been obvious to me ...
In my Javascript on the client I needed to add the \signalr path ...
connection.url = 'http://localhost:45325/signalr';
Perhaps I should have seen that in my public void Configuration(IAppBuilder) method inside the Startup.cs class of my Web Api I had the following ..
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration
{
// EnableJSONP = true; // I am not using as CORS is working just fine
};
map.RunSignalR(hubConfiguration);
});
I was specifying the subdirectory "/signalr"

Calling WCF service with json output from angularjs with windows authentication

I've got a wcf Service which has a method which send its output in jsonformat. The service is hosted in an https-Environment.
i'm calling it with angularjs-resource:
var hrdemo = angular.module('hrdemo', ["ngResource"]);
hrdemo.controller('HrDemoCtrl', function ($scope, hrdbservice) {
$scope.items = hrdbservice.get({ 'Id': 1 });
var a = $scope.items.length;
});
hrdemo.factory('hrdbservice', function ($resource) {
return $resource('http://hrservice/HrService.svc/:Id', { Id: "#Id" }, { get: { method: 'JSONP' } });
});
Angularjs runs in an ASP.Net-Web Application.
When calling the Service I get something like an xhr-problem.
1) How can i authenticate with my Windows authentication over angularjs
2) What can I do to fix the xhr-problem?
May the withCredentials option is what you are searching for $http.post(url, {withCredentials: true, ...}). Also there is a specific shortcut for performing a JSONP request in AngularJS - $http.jsonp()
AngularJS Docs

SignalR external service timed loop - server or client

I have external services that I want to call every 20-30 seconds and make accessible to the client using SignalR but I'm not sure if I should have the client call the server in a JavaScript setInterval loop or if I should have the server run its own loop and push the information to the client. I'm looking to do dynamic page updates to show what a user is currently playing on Xbox 360/Xbox One or PlayStation Network.
Currently, I'm calling the server side code from the client in a setInterval loop, but I'm not sure how well this will scale. I had been doing Web API 2 calls through AJAX previously, but that will quickly eat up bandwidth. The external services are not mine so the only way to get simulated real-time results is to send a request to the external services periodically. The only problem I see with doing a server side loop is thread blocking. I don't want to stop the current thread while the loop is running.
Client side code:
var profile = $.connection.profileHub;
profile.client.receiveXboxProfile = function (profile) {
console.log(profile.GamerTag);
};
profile.client.receivePSNProfile = function (profile) {
console.log(profile.PSNId);
};
$.connection.hub.start()
.done(function () {
function GetGamerProfiles() {
profile.server.sendXboxProfile(window.UserName)
.done(function() {
})
.fail(function (error) {
console.log('SignalR ' + error);
});
profile.server.sendPSNProfile(window.UserName)
.done(function () {
})
.fail(function (error) {
console.log('SignalR ' + error);
});
}
GetGamerProfiles();
var id = setInterval(function () {
GetGamerProfiles();
}, 20000);
});
Server side hub:
[Authorize]
public async Task SendXboxProfile(string username)
{
// Makes call to external service to get Xbox profile
Clients.Caller.receiveXboxProfile(await xboxapi.GetProfile(username));
}
[Authorize]
public async Task SendPSNProfile(string username)
{
// Makes call to external service to get PSN profile
Clients.Caller.receivePSNProfile(await psnapi.GetProfile(username));
}
Maybe SignalR isn't the best solution for this problem since I don't control the external services, but I am very intrigued by the concept of SignalR and HTML 5 websockets. In general, is it better to do periodic updates on the client or server with SignalR?

I need two instances of AngularJS $http service or what?

I want add a response interceptor to my $http service for error handling purposes. The interceptor logic include send errors messages to server using $http in case necessary, BUT I don't want send errors messages to the server about errors messages, I mean, I want disable my interceptor while sending error message to the server.
My idea was create a service named 'remote_log' and put inside it all the code needed to send error to server. That service of course will use the $http service and have it in its dependency list.
Then add as dependency of the interceptor to the 'remote_log' service, and use the 'remote_log' inside the interceptor when need send errors to the server. The problems is that:
Interceptors must be defined using the $httpProvider when the $http service still is not instantiated/accessible, so, inside the interceptor code can't be a dependency to that the $http service because a "Circular dependency" error happen.
I think my only option is create a separate instance of the $http service inside my 'remote_log', an instance that don't uses the $httpProvider configuration I set while creating the interceptor. My question is: How can I do that? Any other ideas?
1. Circular dependency problem.
So, why does the error appear? Here is a quick overview of the process:
$http service is requested.
$httpProvider is asked to construct it.
During construction you register interceptor, that requests $http service not existing yet.
You get "Circular dependency" error.
First solution.
Create your dependency using angular.injector(). Notice, that you will create another $http service, independent from your app.
$httpProvider.interceptors.push(function($q) {
$injector = angular.injector();
return {
response: function(response) {
$injector.invoke(function($http) {
// This is the exterior $http service!
// This interceptor will not affect it.
});
}
};
});
Second solution (better).
Inject $injector in your interceptor and use it to retrieve dependencies after $http initialization, right at the time you need them. These dependencies are registered services of your app and will not be created anew!
$httpProvider.interceptors.push(function($q, $injector) {
return {
response: function(response) {
$injector.invoke(function($http, someService) {
// $http is already constructed at the time and you may
// use it, just as any other service registered in your
// app module and modules on which app depends on.
});
}
};
});
2. Interception prevention problem.
If you use the second solution, there are actually two problems:
If you utilize $http service inside your
interceptor, you may end up with infinite interceptions: you send
request, interceptor catches it, sends another, catches another,
send again, and so on.
Sometimes you want just prevent request from being intercepted.
The 'config' parameter of $http service is just an object. You may create a convention, providing custom parameters and recognizing them in your interceptors.
For example, let's add "nointercept" property to config and try duplicate every user request. This is a silly application, but useful example to understand the behavior:
$httpProvider.interceptors.push(function($q, $injector) {
return {
response: function(response) {
if (response.config.nointercept) {
return $q.when(response); // let it pass
} else {
var defer = $q.defer();
$injector.invoke(function($http) {
// This modification prevents interception:
response.config.nointercept = true;
// Reuse modified config and send the same request again:
$http(response.config)
.then(function(resp) { defer.resolve(resp); },
function(resp) { defer.reject(resp); });
});
return defer.promise;
}
}
};
});
Having the testing for property in interceptor, you may prevent the interception in controllers and services:
app.controller('myController', function($http) {
// The second parameter is actually 'config', see API docs.
// This query will not be duplicated by the interceptor.
$http.get('/foo/bar', {nointercept: true})
.success(function(data) {
// ...
});
});
I used what is described in the answer but I used the syntax with a factory because with the anonymous function it didn't work, I don't really know why:
(function(angular){
angular.module('app', [])
.config([
'$httpProvider',
function($httpProvider) {
$httpProvider.interceptors.push('Interceptor');
}
])
.factory('Interceptor', [
'$injector',
InterceptorFactory
]);
function InterceptorFactory($injector){
return {
request: function(config) {
var ServiceWithHttp = $injector.get('ServiceWithHttp');
// Use ServiceWithHttp
return config;
}
};
}
}(window.angular));

Resources