SignalR generates lots of 403 errors - c#

I've created small module for website, which uses SignalR to detect if user needs to refresh the browser. Locally it works, but when code went into production, thousands of errors are produced:
request: (lots of pages from website)
referer: https://(website)/signalr/abort
error message: 403: Http Error 403
Server side: (mainProject/Hubs directory):
public class AppVersionNotifierHub : Hub<IAppVersionNotifierHub>
{
public void CheckAppVersion(string version)
{
// if client has diffrent version, invoke callback
if (Global.Version != version)
{
Clients.Caller.NewAppVersion(Global.Version);
}
}
}
Javascript (type script):
this.subscribeVersionChecker = () => {
var hub = (<any>$.connection).appVersionNotifierHub;
hub.client.newAppVersion = (version: string) => {
.. some logic
}
$.connection.hub.start(() => {
hub.server.checkAppVersion(customerVersion.text());
});
$.connection.hub.reconnected(() => {
setTimeout(() => {
hub.server.checkAppVersion(customerVersion.text());
}, 5000); // Restart connection after 5 seconds.
});
$.connection.hub.disconnected(() => {
setTimeout(() => {
$.connection.hub.start();
}, 10000); // Restart connection after 10 seconds.
});
};
Any ideas why some clients generates errors ?
Site is hosted on azure
To use bundles, I've copied dynamically generated signalr.js file into Scripts\signalrhub.js file

I have discovered the problem now.
Problem was with authentication, I've read this article: http://www.bitwisejourneys.com/signalr-authenticating-even-when-you-dont-think-it-is/ and after some thinking I was able to reproduce the problem.
Open site in one tab and log in
Open site in the other tab and log out
Restart the connection on server
Tab 1 is sending the same authentication token as Tab 2. Server denies Tab 1 but responds to Tab 2
My solution: when connection is lost, I'm not trying to reconnect, but to stop and restart the connection (note I've changed the event from Reconnected to Reconnecting !)
$.connection.hub.reconnecting(() => {
$.connection.hub.stop();
setTimeout(() => {
$.connection.hub.start();
}, 5000); // Restart connection after 5 seconds.
});

Related

Azure Signal R Service with Angular 13

I've been trying to setup an integration between an Angular 13 client-side app and a .NET Framework 4.8 API with an Azure Signal R Service. After much trial and error this week I have finally got my api to build without CORS errors and I can make a successful negotiate call using postman. I am using the #aspnet/signalr client-side package since that is the only signal R package that support Azure Signal R Services.
Below is the angular code I am using to make a connection to my signal R hub named DashboardHub
import { Injectable } from '#angular/core';
import * as signalR from '#aspnet/signalr';
#Injectable()
export class SignalrService {
public initializeSignalRConnection(): void {
let connection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:58556/signalr/dashboardhub")
.configureLogging(signalR.LogLevel.Debug)
.build();
connection.start()
.then(() => {
console.log("connection.start");
//connection.invoke("send", "Hello")
})
.catch((error) => {
console.log("connection start error", error);
});
connection.on("send", data => {
console.log(data);
});
}
}
The problem I am having is within the connection.start which returns an error Cannot read properties of undefined (reading 'length'). Screenshot of that browser error below.
API Signal R mapping for context
And here is the DashboardHub for reference

Google web authorization broker and electron desktop app

Background:
My project consists of a Vue 2 Front end encased in an electron shell with an asp.net core web API backend.
What I am trying to do is get a google authorization token and refresh token so that I can create a folder and files within that folder on a users google drive. I also want to display a list of the folders content in my front end.
I have tried using the new Google Identity Services code in my front end but when launching my app and clicking on the google button I the following error
Error 400: invalid_request
If you’re the app developer, make sure that these request details comply with Google policies:
redirect_uri: storagerelay://file/?id=auth12850
I think this is because electron is seen more of a desktop app which according to the google documentation needs to use a loopback address to open the system browser and authenticate from there.
So I tried the google authentication api on my backend which is C# I have the google web authorization broker setup and when I run my backend and call my endpoint I get the google sign in page and can get the authorization token and refresh token.
Question:
Is there a way to capture the URL of the page that comes up for authentication so that I can put it in a child window in electron.
or a way to pass the data to the front end so that I can show the list of files to my users.
Would I still need to use a loopback address even though I am getting the authentication page from the web broker?
If I do need the loopback functionality would I be better off using the data from the Google Desktop Application example rather than the google Web broker?
To learn more about electron you can go Electron website.
For a web app I think your going to have an issue
Users will be redirected to this path after they have authenticated with Google. The path will be appended with the authorization code for access, and must have a protocol. It can’t contain URL fragments, relative paths, or wildcards, and can’t be a public IP address.
Not just because of the format but because its going to need to be a domain you can register.
If you go with an installed app then the redirect uri is https://127.0.0.1
Im not sure how you are going to get this to route back properly.
I ended up using the nodejs google api to get this working this is my code that now returns an auth token and refresh token.
This code opens a child window when the authorize button is clicked and loads the google login/account select. Once authorized it shows the app permission window. When a user clicks allow the url is invoked in the loopback of the created server and the auth file is created in the specified directory.
part one successful.. now on to getting everything else working.
/* Google authentication */
function createGoogleWindow() {
const http = require('http');
const path = require('path');
const service = google.drive('v3');
const TOKEN_DIR = path.join(process.env.APPDATA, 'home-inventory', 'bin');
const TOKEN_PATH = path.join(TOKEN_DIR, 'home-inventory.json');
const querystring = require('querystring');
let googleWindow = new BrowserWindow({
parent: win,
height: 600,
width: 400,
webPreferences: {
webSecurity: false,
nodeIntegration: true,
enableRemoteModule: true,
contextIsolation: false
}
});
if (isDevelopment) googleWindow.webContents.openDevTools();
googleWindow.menuBarVisible = false;
googleWindow.on('closed', () => {
googleWindow = null;
});
const oauth2Client = new google.auth.OAuth2(
CLIENT_ID,
CLIENT_SECRET,
REDIRECT_URI
);
// check if we previously stored a token
fs.readFile(TOKEN_PATH, function (err, token){
if (err) {
getNewToken(oauth2Client);
} else {
oauth2Client.credentials = JSON.parse(token);
callback(oauth2Client);
}
});
const url = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES
});
function callback (auth) {
service.files.list({
auth: auth,
q: `name contains '.bak'`,
pageSize: 50,
fields: "nextPageToken, files(id,name,size,parents,createdTime)",
}, function(err, response) {
if (err) {
console.error('The API returned an error: ', err);
return;
}
const files = response.files;
if (files.length === 0) {
console.warn('no files found');
} else {
console.warn('files', files, auth.credentials.access_token);
}
});
}
function getNewToken(oauth2Client, callback) {
function storeToken(token) {
try {
fs.mkdirSync(TOKEN_DIR);
} catch (err) {
if (err.code !== 'EEXIST') {
throw err
}
}
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) {
throw err;
}
// console.debug('file was saved successfully');
googleWindow.close();
});
// console.warn('Token stored to: ', TOKEN_PATH);
}
function handler(request, response, server, callback) {
let qs = querystring.parse(require('url').parse(request.url).query);
oauth2Client.getToken(qs.code, function (err, tokens) {
if (err) {
console.error('Error getting oAuth tokens: ', err);
}
oauth2Client.credentials = tokens;
storeToken(tokens);
callback(oauth2Client)
server.close();
});
}
const server = http.createServer(function (request, response) {
handler(request, response, server, callback);
}).listen(8181, function() {
googleWindow.loadURL(url);
})
}
}

Azure SignalR Blazor app not receiving messages

I'm looking at incorporating Azure SignalR functionality into my .net core Blazor web application. To this end i've been following this tutorial - Azure Signalr Serverless. This is working fine - i have a project running the Azure functions app and can start up two browsers and have a chat session. What i'm trying to do is add the ability to receive these message notifications from the Azure signalR hub that's been configured into my Blazor app. I've added the following code in Index.razor.cs that mimics the javascript code in the example client:
public class IndexComponent : ComponentBase
{
private HubConnection _connection;
public string Message;
protected override Task OnInitializedAsync()
{
_connection = new HubConnectionBuilder()
.WithUrl("http://localhost:7071/api")
.Build();
_connection.On<string, string>("ReceiveMessage", (user, message) =>
{
Message = $"Got message {message} from user {user}";
this.StateHasChanged();
});
_connection.StartAsync();
return base.OnInitializedAsync();
}
}
The example javascript code btw is:
const connection = new signalR.HubConnectionBuilder()
.withUrl(`${apiBaseUrl}/api`)
.configureLogging(signalR.LogLevel.Information)
.build();
connection.on('newMessage', newMessage);
connection.onclose(() => console.log('disconnected'));
console.log('connecting...');
connection.start()
.then(() => data.ready = true)
.catch(console.error);
So the problem is that my Blazor app never receives any message notifications sent from the javascript chat clients (so the _connection.On handler is never hit). What am i missing in my Blazor code ?
Ok so this is what i needed to do to get it to work in my Blazor app:
_connection.On<object>("newMessage", update =>
{
Console.WriteLine(update);
//Message = update;
});
I needed to subscribe to the 'newMessage' target (since that's the JS is sending on) and also the type that's being posted isn't a string but a JObject type which i would need to deserialize to the correct type.

Calling SignalR from API at another project - No error nor notification

I have a WebSite integrated with SignalR. It functions well, and it has a button which sends popup notification to all clients who are online. It works well when I click on the button.
My API is in another project but in the same Solution. I want to send the above notification by calling from the API side. Basically, a mobile app will send a request to API and then API will send a notification to all online web clients.
Below code runs and not gives the notification nor any error.
Is this fundamentally correct? Appreciate your help
API code (at WebAPI project)
[HttpGet]
public IEnumerable<string> WatchMe(int record_id)
{
GMapChatHub sendmsg = new GMapChatHub();
sendmsg.sendHelpMessage(record_id.ToString());
return "Done";
}
C# code (at Web project)
namespace GMapChat
{
public class GMapChatHub : Hub
{
public void sendHelpMessage(string token)
{
var context = GlobalHost.ConnectionManager.GetHubContext<GMapChatHub>();
context.Clients.All.helpMessageReceived(token, "Test help message");
}
}
}
Home.aspx file (at Web project)
var chat = $.connection.gMapChatHub;
$(document).ready(function () {
chat.client.helpMessageReceived = function (token,msg) {
console.log("helpMessageReceived: " + msg);
$('#helpMessageBody').html(msg)
$('#helpModal').modal('toggle');
};
}
You can not call that hub directly. Firs you need to install the .net client for SignalR from nuget. Then you need to initialize it like this :
[HttpGet]
public IEnumerable<string> WatchMe(int record_id)
{
using (var hubConnection = new HubConnection("your local host address"))
{
IHubProxy proxy= hubConnection.CreateHubProxy("GMapChatHub");
await hubConnection.Start();
proxy.Invoke("sendHelpMessage",record_id.ToString()); // invoke server method
}
// return sth. IEnumerable<string>
}
And opening a new connection per request may not be good idea you may make it per session (if you use) or static or time fashioned.

signalR with multiple client connections

I have written an Application where I am using SignalR. I am sending connectionId from Client to Server(controller).
Everything is working fine with single browser (request will sent to server with connectionId="conn_1") and signalR is sending response to only conn_1, but when i open new browser and send a request from that client the previous connection gets disposed. Which means only one connection with particular connectionId remains alive.
Is there any way SignalR can not dispose and send response to both with data they want?
I am new to SignalR and would really appropriate any help or guidance.
Angular SignalRService to start connection with server
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(this.paymentDraftHubUrl)
.build();
return this.hubConnection
.start()
.then(() => this.hubConnectionStatus = 'Connection started')
.catch(err => (this.hubConnectionStatus = 'Error while starting connection: ' + err));
}
sending connectionId from client component to Api
this.signalRService.startConnection().then((connection) => {
this.connectionId = connection.toString();
//Calling Api
this.getAllTransactionException(
this.connectionId,
this.pageNumber,
this.pageSize
}
MyHub class in C#
public class PaymentDraftServiceHub : Hub, IPaymentDraftHub
{}
Controller for API
using timer to keep calling repository for new data,
[HttpGet]
[Route("GetCsrTranactions")]
public IActionResult GetCsrTranactions([FromQuery] TransactionExceptionDataRequest queryParams)
{
TimeManager.Dispose();
var timerManager = new TimeManager(async () =>
await _paymentDraftHub.Clients.Clients.Client(queryParams.ConnectionId).SendAsync(SignalRConstants.TransferPaymentDraftServiceData, await _paymentTransactionRepository.GetCsrTranactionsAsync(queryParams)));
var response = new ResponseMessage { Message = "Accepted", Code = "201" };
return Ok(response);
}
Client can have multiple connections with multiple connection IDs if client connect from multiple browser windows or tabs.
According to the code you provided, we can find that you just pass connection ID of SignalR client within current active browser tab/window to your controller, and in your controller action, you use this code snippet .Client(queryParams.ConnectionId).SendAsync() to send message to a specific client, so other browser windows or tabs would not receive the message.
If you'd like to send message(s) to a client with multiple connections, you need to map SignalR users to connection Ids and retain information about users-to-connectionIds mapping, then you can get all connectionIds of a client and send messages to that client with with multiple connectionIds, like below.
//code logic here
//to get all connectinIds of a client/user
//from user-to-connectionIds mapping table
await _paymentDraftHub.Clients.Clients(connectionIds_here).SendAsync("method_here",args_here);

Categories

Resources