SignalR Core not generating(mapping) Client methods - asp.net-core

The SignalR Core is generating Hub proxies script, but not adding the "client" methods. (No errors in server or client - only not working)
Generated JS from <script src="http://localhost/signalr/hubs">
proxies['messageHub'] = this.createHubProxy('messageHub');
proxies['messageHub'].client = { };
proxies['messageHub'].server = {
handleMessage: function (receivedString) {
return proxies['messageHub'].invoke.apply(proxies['messageHub'], $.merge(["HandleMessage"], $.makeArray(arguments)));
}
};
Here's the Hub in Server Side:
public class MessageHub : Hub
{
public void HandleMessage(string receivedString)
{
var responseString = string.Empty;
MessageHandler.HandleMessage(receivedString, ref responseString);
Clients.All.sendMessage(responseString);
}
}
The sendMessage methos should be included in the messageHub client proxies in the JS file.
$.connection.messageHub.client.sendMessage is undefined
Only the handleMessage for server proxies was created (and working).
Here's my StartUp.cs inclusions for SignalR:
ConfigureServices:
services.AddMvc(options =>
{
options.Filters.Add(new RoleFilterAttribute());
}).AddJsonOptions(options => options.SerializerSettings.ContractResolver =
new DefaultContractResolver());
services.AddSignalR(options => options.Hubs.EnableDetailedErrors = true)
Configure:
app.UseWebSockets();
app.UseSignalR();
project.json:
"Microsoft.AspNetCore.Mvc": "1.0.0-*",
"Microsoft.AspNetCore.WebSockets": "1.0.0",
"Microsoft.AspNetCore.SignalR.Server": "0.2.0-*",
SOME ADDITIONAL TENTATIVES:
1 - Change method case in Server Side to see if it's mapped:
Clients.All.SendMessage(responseString);
Did not work!
2 - Change the client side to dynamic mapping:
var connection = $.hubConnection('http://localhost/');
var proxy = connection.createHubProxy('messageHub');
connection.start({ withCredentials: false }).done(function () { console.log("CONNECTED") });
proxy.on("sendMessage", function (result) {console.log(result);});
proxy.invoke("handleMessage", msg).done(function(result)console.log(result);});
Again only the handleMessage (server) worked.

Well according to the docs you are missing method name so the send all line should look like this
public void HandleMessage(string receivedString)
{
var responseString = string.Empty;
MessageHandler.HandleMessage(receivedString, ref responseString);
Clients.All.SendMessage("SendMessage",responseString);
}
Also in the following is the correct way
app.UseSignalR(routes =>
{
routes.Hub<MessageHub>("/messageHub");
});
and finally
var connection = $.hubConnection('http://localhost/');
var proxy = connection.createHubProxy('messageHub');
connection.start({ withCredentials: false }).done(function () { console.log("CONNECTED") });
proxy.on("SendMessage", function (result) {console.log(result);});
proxy.invoke("HandleMessage", msg).done(function(result)console.log(result);});

ASP.NET Core SignalR doesn't generate client proxies. There's good advice in the comment to follow the tutorial https://learn.microsoft.com/en-us/aspnet/core/tutorials/signalr?view=aspnetcore-6.0&tabs=visual-studio

Related

SignalR Azure Service with stand alone Identity Server 4 returns 401 on negotiaton

We have a ASP.Net Core application that authenticates against a standalone Identity Server 4. The ASP.Net Core app implements a few SignalR Hubs and is working fine when we use the self hosted SignalR Service. When we try to use the Azure SignalR Service, it always returns 401 in the negotiation requests. The response header also states that
"Bearer error="invalid_token", error_description="The signature key
was not found"
I thought the JWT-Configuration is correct because it works in the self hosted mode but it looks like, our ASP.Net Core application needs information about the signature key (certificate) that our identity server uses to sign the tokens. So I tried to use the same method like our identity server, to create the certificate and resolve it. Without luck :-(
This is what our JWT-Configuration looks like right now:
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options => {
var appSettings = Configuration.Get<AppSettingsModel>();
options.Authority = appSettings.Authority;
options.RefreshOnIssuerKeyNotFound = true;
if (environment.IsDevelopment()) {
options.RequireHttpsMetadata = false;
}
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters {
ValidateAudience = false,
IssuerSigningKey = new X509SecurityKey(getSigningCredential()),
IssuerSigningKeyResolver = (string token, SecurityToken securityToken, string kid, TokenValidationParameters validationParameters) =>
new List<X509SecurityKey> { new X509SecurityKey(getSigningCredential()) }
};
options.Events = new JwtBearerEvents {
OnMessageReceived = context => {
var accessToken = "";
var headerToken = context.Request.Headers[HeaderNames.Authorization].ToString().Replace("Bearer ", "");
if (!string.IsNullOrEmpty(headerToken) && headerToken.Length > 0) {
accessToken = headerToken;
}
var queryStringToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(queryStringToken) && queryStringToken.ToString().Length > 0) {
accessToken = queryStringToken;
}
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/hubs")) {
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
Update:
We also have a extended the signalR.DefaultHttpClient in our Angular Client and after playing around a bit, I noticed the application is working fine without it:
export class CustomSignalRHttpClientService extends signalR.DefaultHttpClient {
userSubscription: any;
token: string = "";
constructor(private authService: AuthorizeService) {
super(console); // the base class wants a signalR.ILogger
this.userSubscription = this.authService.accessToken$.subscribe(token => {
this.token = token
});
}
public async send(
request: signalR.HttpRequest
): Promise<signalR.HttpResponse> {
let authHeaders = {
Authorization: `Bearer ${this.token}`
};
request.headers = { ...request.headers, ...authHeaders };
try {
const response = await super.send(request);
return response;
} catch (er) {
if (er instanceof signalR.HttpError) {
const error = er as signalR.HttpError;
if (error.statusCode == 401) {
console.log('customSignalRHttpClient -> 401 -> TokenRefresh')
//token expired - trying a refresh via refresh token
this.token = await this.authService.getAccessToken().toPromise();
authHeaders = {
Authorization: `Bearer ${this.token}`
};
request.headers = { ...request.headers, ...authHeaders };
}
} else {
throw er;
}
}
//re try the request
return super.send(request);
}
}
The problem is, when the token expires while the application is not open (computer is in sleep mode e.g.), the negotiaton process is failing again.
I finally found and solved the problem. The difference of the authentication between "self hosted" and "Azure SignalR Service" is in the negotiation process.
Self Hosted:
SignalR-Javascript client authenticates against our own webserver with
the same token that our Javascript (Angular) app uses. It sends the
token with the negotiation request and all coming requests of the
signalR Http-Client.
Azure SignalR Service:
SignalR-Javascript client sends a negotiation request to our own
webserver and receives a new token for all coming requests against the
Azure SignalR Service.
So our problem was in the CustomSignalRHttpClientService. We changed the Authentication header to our own API-Token for all requests, including the requests against the Azure SignalR Service -> Bad Idea.
So we learned that the Azure SignalR Service is using it's own token. That also means the token can invalidate independently with our own token. So we have to handle 401 Statuscodes in a different way.
This is our new CustomSignalRHttpClientService:
export class CustomSignalRHttpClientService extends signalR.DefaultHttpClient {
userSubscription: any;
token: string = "";
constructor(private authService: AuthorizeService, #Inject(ENV) private env: IEnvironment, private router: Router,) {
super(console); // the base class wants a signalR.ILogger
this.userSubscription = this.authService.accessToken$.subscribe(token => {
this.token = token
});
}
public async send(
request: signalR.HttpRequest
): Promise<signalR.HttpResponse> {
if (!request.url.startsWith(this.env.apiUrl)) {
return super.send(request);
}
try {
const response = await super.send(request);
return response;
} catch (er) {
if (er instanceof signalR.HttpError) {
const error = er as signalR.HttpError;
if (error.statusCode == 401 && !this.router.url.toLowerCase().includes('onboarding')) {
this.router.navigate([ApplicationPaths.Login], {
queryParams: {
[QueryParameterNames.ReturnUrl]: this.router.url
}
});
}
} else {
throw er;
}
}
//re try the request
return super.send(request);
}
}
Our login-Route handles the token refresh (if required). But it could also happen, that our own api-token is still valid, but the Azure SignalR Service token is not. Therefore we handle some reconnection logic inside the service that creates the SignalR Connections like this:
this.router.events.pipe(
filter(event => event instanceof NavigationEnd)
).subscribe(async (page: NavigationEnd) => {
if (page.url.toLocaleLowerCase().includes(ApplicationPaths.Login)) {
await this.restartAllConnections();
}
});
hope this helps somebody

Why am I getting no data from my Azure SignalR Service back to my KendoUI Grid?

I'm using a KendoUI Grid in my ASP.NET Core 5 application. The grid is SignalR enabled and my application is hosted in Azure. I also use the Azure SignalR Service to handle the hubs which is what is recommended by the documentation.
I have 4 hubs powering my 4 different grids and yet, none of them are receiving data back from the SignalR Service in Azure it seems and I have no idea what is wrong. The grids seem to load, missing data, I can see the negotiations in the console with signalR and they return 200 OK. However, there is just no data being returned.
I'm not sure if it's a problem with the way I've set up my application or my Azure SignalR Service.
Here is how I have implemented the Azure SignalR Service in my application.
Startup.cs (ConfigureServices)
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var mvcBuilder = services.AddControllersWithViews(options => {
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
mvcBuilder.AddMicrosoftIdentityUI().AddJsonOptions(options =>
{
//Use the default property (Pascal) casing.
options.JsonSerializerOptions.PropertyNamingPolicy = null;
});
services.AddRazorPages();
services.AddSignalR(options => {
//Debug only
options.EnableDetailedErrors = true;
}).AddAzureSignalR()
.AddJsonProtocol(options =>
{
//Prevents signalr converting all text to lower case.
options.PayloadSerializerOptions.PropertyNamingPolicy = null;
});
//Kendo
services.AddKendo();
services.AddHealthChecks();
}
Startup.cs (Configure)
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
//Localization needed for date formats on datepickers
var supportedCultures = new[] {
new CultureInfo("en-GB")
};
//Localization set to En GB for datetime using the above supported cultures
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture("en-GB"),
SupportedCultures = supportedCultures,
SupportedUICultures = supportedCultures
});
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/healthcheck");
endpoints.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
app.UseAzureSignalR(endpoints =>
{
endpoints.MapHub<RequirementHub>("/requirementHub");
endpoints.MapHub<PositionHub>("/positionHub");
endpoints.MapHub<FixtureHub>("/fixtureHub");
endpoints.MapHub<NewswireHub>("/newswireHub");
});
}
I am using SignalR installed in my client library in it's latest version.
libman.js
{
"provider": "unpkg",
"library": "#microsoft/signalr#latest",
"destination": "wwwroot/vendor/signalr/",
"files": [
"dist/browser/signalr.js",
"dist/browser/signalr.min.js"
]
},
I have hub controllers for each of my grids that uses SignalR. I have shown the code for one of the hub controllers but they are all structured exactly the same, the only thing that changes is the name of the repository they are getting data from.
RequirementHub
using MyCompany.Data;
using MyCompany.Repo;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.SignalR;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MyCompany.UI.Hubs
{
public class RequirementHub : Hub
{
private readonly IRepository<Requirement> _requirement;
public RequirementHub(IRepository<Requirement> requirement)
{
_requirement = requirement;
}
public override Task OnConnectedAsync()
{
Groups.AddToGroupAsync(Context.ConnectionId, GetGroupName());
return base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception e)
{
Groups.RemoveFromGroupAsync(Context.ConnectionId, GetGroupName());
return base.OnDisconnectedAsync(e);
}
public IEnumerable<Requirement> Read()
{
var data = _requirement.GetAll();
return data;
}
public async Task Update(Requirement model)
{
await _requirement.UpdateAsync(model);
await Clients.OthersInGroup(GetGroupName()).SendAsync("update", model);
}
public string GetGroupName()
{
return GetRemoteIpAddress();
}
public string GetRemoteIpAddress()
{
return Context.GetHttpContext()?.Connection.RemoteIpAddress.ToString();
}
}
}
This now brings us to the grid itself. Here is the code for the requirement grid, again, there are 4 grids and they are all structured the same but with different names and referencing their respective hubs.
Home.cshtml
<div id="requirement-grid"></div>
<script>
$('#requirement_grid').kendoGrid({
dataSource: {
type: "signalr",
autoSync: true,
pageSize: 20,
sort: [
{
field: "Id",
dir: "desc"
}
],
schema: {
model: {
id: "Id",
fields: {
"Id": {
editable: false,
nullable: true
}
}
}
},
transport: {
signalr: {
promise: requirement_hub_start,
hub: requirement_hub,
server: {
read: "read",
update: "update",
create: "create",
destroy: "destroy"
},
client: {
read: "read",
update: "update",
create: "create",
destroy: "destroy"
}
}
},
autoBind: true,
reorderable: true,
sortable: true,
pageable: {
pageSize: 30,
refresh: true
},
columns: [
{
field: "Id"
}
]
});
</script>
You'll notice that the promise and hub are defined as requirement_hub_start and requirement_hub the code for those lives in its own JavaScript file as:
hubs.js
//Hub URL
var requirement_url = "/requirementHub";
var fixture_url = "/fixtureHub";
var position_url = "/positionHub";
var newswire_url = "/newswireHub";
//Connection Builder
var requirement_hub = new signalR.HubConnectionBuilder().withUrl(
requirement_url, {
transport: signalR.HttpTransportType.LongPolling
}).build();
var position_hub = new signalR.HubConnectionBuilder().withUrl(
position_url, {
transport: signalR.HttpTransportType.LongPolling
}).build();
var fixture_hub = new signalR.HubConnectionBuilder().withUrl(
fixture_url, {
transport: signalR.HttpTransportType.LongPolling
}).build();
var newswire_hub = new signalR.HubConnectionBuilder().withUrl(
newswire_url, {
transport: signalR.HttpTransportType.LongPolling
}).build();
//Hub Start
var position_hub_start = position_hub.start({
json: true
});
var requirement_hub_start = requirement_hub.start({
json: true
});
var fixture_hub_start = fixture_hub.start({
json: true
});
var newswire_hub_start = newswire_hub.start({
json: true
});
I looked at the documentation and have placed my SignalR Service connection string in my appsettings.json (until I can get this working):
appsettings.json
"Azure": {
"SignalR": {
"ConnectionString": "Endpoint=https://mycompanysignalr.service.signalr.net;AccessKey=xxx=;Version=1.0;",
"Enabled": "true"
}
}
Additionally, I have ensured that web sockets are switched ON in my Azure web app which was another recommendation of the documentation. I'm a little confused at this point, I've been over my setup 100 times and I can't see anything obvious that could be preventing data coming back.
Is there another step I need to follow or have I done something wrong? Has anyone had this problem before?
Please check the Signalr ConnectionStrings in your appsettings.json. You can copy it from azure portal and replace it, then try.
The above suggestion is to consider whether you have used different Signalr ConnectionStrings from the Dev environment to the production environment, which caused this problem.
In addition, what pricing tier are you using? If you use Free tier, azure signalr service will limit your use.
That will happen, you have no problem at the beginning, the program is normal, and when the limit is exceeded, the program will be abnormal. At the beginning of the next day, it can be used for a while. This is a restriction given by the Free pricing tier.

AddWebhookNotification to call Method in Controller

I have this configured in my StartUp.cs:
public void ConfigureServices(IServiceCollection services)
{
services
.ConfigureEmail(Configuration)
.AddHealthChecksUI(setupSettings: setup =>
{
setup
.AddWebhookNotification("WebHookTest", "/WebhookNotificationError",
"{ message: \"Webhook report for [[LIVENESS]]: [[FAILURE]] - Description: [[DESCRIPTIONS]]\"}",
"{ message: \"[[LIVENESS]] is back to life\"}",
customMessageFunc: report =>
{
var failing = report.Entries.Where(e => e.Value.Status == UIHealthStatus.Unhealthy);
return $"{failing.Count()} healthchecks are failing";
},
customDescriptionFunc: report =>
{
var failing = report.Entries.Where(e => e.Value.Status == UIHealthStatus.Unhealthy);
return $"HealthChecks with names {string.Join("/", failing.Select(f => f.Key))} are failing";
});
})
.AddControllers();
}
public void Configure(IApplicationBuilder app)
{
var pathBase = Configuration["PATH_BASE"];
if (!string.IsNullOrEmpty(pathBase))
{
app.UsePathBase(pathBase);
}
app.ConfigureExceptionHandler();
app
.UseRouting()
.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecksUI(options =>
{
options.ResourcesPath = string.IsNullOrEmpty(pathBase) ? "/ui/resources" : $"{pathBase}/ui/resources";
options.UIPath = "/hc-ui";
options.ApiPath = "/api-ui";
});
endpoints.MapDefaultControllerRoute();
});
}
And in the Controller:
[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
public async Task<IActionResult> WebhookNotificationError([FromBody] string id)
{
MimeMessage mimeMessage = new MimeMessage { Priority = MessagePriority.Urgent };
mimeMessage.To.Add(new MailboxAddress(_configuration.GetValue<string>("ConfiguracionCorreoBase:ToEmail")));
mimeMessage.Subject = "WebHook Error";
BodyBuilder builder = new BodyBuilder { HtmlBody = id };
mimeMessage.Body = builder.ToMessageBody();
await _appEmailService.SendAsync(mimeMessage);
return Ok();
}
The watchdog application is configured in the appSettings.json to listen to different APIs.
So far everything works fine, but, if I force an error, I'd like to receive a notification email.
The idea is that, when an error occurs in any of the Healths, you send an email.
Environment:
.NET Core version: 3.1
Healthchecks version: AspNetCore.HealthChecks.UI 3.1.0
Operative system: Windows 10
It's look like you problem in routes. Did you verify that method with Postman?
Also check if your webHook request body is a text, try to change your template payload:
{ "message": "Webhook report for [[LIVENESS]]: [[FAILURE]] - Description: [[DESCRIPTIONS]]"}",
and in the controller change string to object. And check what you receive in DEBUG.
Try using Api/WebhookNotificationError inst. of /WebhookNotificationError if your controller is ApiController. The controller name seems to be missing
I think you should try this. It works for me.
[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
public async Task<IActionResult> WebhookNotificationError()
{
using (var reader = new StreamReader(
Request.Body,
encoding: Encoding.UTF8,
detectEncodingFromByteOrderMarks: false))
{
var payload = await reader.ReadToEndAsync();
//do whatever with your payloade here..
//I am just returning back for a example.
return Ok(payload);
}
}

Blazor WebAssembly SignalR Authentication

I would love to see an example on how to add authentication to a SignalR hub connection using the WebAssembly flavor of Blazor. My dotnet version is 3.1.300.
I can follow these steps to get an open, unauthenticated SignalR connection working: https://learn.microsoft.com/en-us/aspnet/core/tutorials/signalr-blazor-webassembly?view=aspnetcore-3.1&tabs=visual-studio
All the tutorials I find seem older or are for a server-hosted type, and don't use the built-in template.
I have added authentication to the rest of the back-end, using the appropriate template and these instructions, including the database:
https://learn.microsoft.com/en-us/aspnet/core/security/blazor/?view=aspnetcore-3.1
But every time I add [Authenticate] to the chat hub, I get an error returned. Is there any way, extending the first tutorial, that we can authenticate the hub that is created there? It would be great to hitch on to the built-in ASP.NET system, but I am fine just passing a token in as an additional parameter and doing it myself, if that is best. In that case I would need to learn how to get the token out of the Blazor WebAssembly, and then look it up somewhere on the server. This seems wrong, but it would basically fill my needs, as an alternative.
There are all sorts of half-solutions out there, or designed for an older version, but nothing to build off the stock tutorial that MS presents.
Update:
Following the hints in this news release https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-preview-2-release-now-available/, I now can get a token from inside the razor page, and inject it into the header. I guess this is good?? But then how do I get it and make use of it on the server?
Here is a snippet of the razor code:
protected override async Task OnInitializedAsync()
{
var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(UriHelper.BaseUri);
var tokenResult = await AuthenticationService.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
{
httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {token.Value}");
hubConnection = new HubConnectionBuilder()
.WithUrl(UriHelper.ToAbsoluteUri("/chatHub"), options =>
{
options.AccessTokenProvider = () => Task.FromResult(token.Value);
})
.Build();
}
}
Update 2:
I tried the tip in here: https://github.com/dotnet/aspnetcore/issues/18697
And changed my code to:
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/chatHub?access_token=" + token.Value))
.Build();
But no joy.
I've come across the same issue.
My solution was 2-sided: I had to fix something in the fronend and in the backend.
Blazor
In your connection builder you should add the AccessTokenProvider:
string accessToken = "eyYourToken";
connection = new HubConnectionBuilder()
.WithUrl("https://localhost:5001/hub/chat", options =>
{
options.AccessTokenProvider = () => Task.FromResult(token.Value);
})
.Build();
options.AccessTokenProvider is of type Func<Task<string>>, thus you can also perform async operations here. Should that be required.
Doing solely this, should allow SignalR to work.
Backend
However! You might still see an error when SignalR attempts to create a WebSocket connection. This is because you are likely using IdentityServer on the backend and this does not support Jwt tokens from query strings. Unfortunately SignalR attempts to authorize websocket requests by a query string parameter called access_token.
Add this code to your startup:
.AddJwtBearer("Bearer", options =>
{
// other configurations omitted for brevity
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hubs"))) // Ensure that this path is the same as yours!
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
edit 1: Clarified the usage of the Blazor SignalR code
In my case (Blazor WebAssembly, hosted on ASP.NET Core 5.0 using JWT Bearer Token Auth), I had to add the following:
Blazor WASM Client
When building the connection (in my case: in the constructor of some service proxy class), use IAccessTokenProvider and configure the AccessTokenProvider option like so:
public ServiceProxy(HttpClient httpClient, IAccessTokenProvider tokenProvider) {
HubConnection = new HubConnectionBuilder()
.WithUrl(
new Uri(httpClient.BaseAddress, "/hubs/service"),
options => {
options.AccessTokenProvider = async () => {
var result = await tokenProvider.RequestAccessToken();
if (result.TryGetToken(out var token)) {
return token.Value;
}
else {
return string.Empty;
}
};
})
.WithAutomaticReconnect() // optional
.Build();
}
ASP.NET Core Server
Add the following to Startup.ConfigureServices:
services.Configure<JwtBearerOptions>(JwtBearerDefaults.AuthenticationScheme, options => {
// store user's "name" claim in User.Identity.Name
options.TokenValidationParameters.NameClaimType = "name";
// pass JWT bearer token to SignalR connection context
// (from https://learn.microsoft.com/en-us/aspnet/core/signalr/authn-and-authz?view=aspnetcore-5.0)
options.Events = new JwtBearerEvents {
OnMessageReceived = context => {
var accessToken = context.Request.Query["access_token"];
// If the request is for on of our SignalR hubs ...
if (!string.IsNullOrEmpty(accessToken) &&
(context.HttpContext.Request.Path.StartsWithSegments("/hubs/service"))) {
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
This is my solution and works
[Inject] HttpClient httpClient { get; set; }
[Inject] IAccessTokenProvider tokenProvider { get; set; }
HubConnection hubConnection { get; set; }
(...)
private async Task ConnectToNotificationHub()
{
string url = httpClient.BaseAddress.ToString() + "notificationhub";
var tokenResult = await tokenProvider.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
{
hubConnection = new HubConnectionBuilder().WithUrl(url, options =>
{
options.Headers.Add("Authorization", $"Bearer {token.Value}");
}).Build();
await hubConnection.StartAsync();
hubConnection.Closed += async (s) =>
{
await hubConnection.StartAsync();
};
hubConnection.On<string>("notification", m =>
{
string msg = m;
});
}
}

Connect signalr with vue / vuex

I am trying to connect SignalR hub to a Vue component but I fail doing that. i googled "vue with signalr" and real almost every link up to second page.
I getting a cors origin, but I dont think that this is the main problem, since my post/get call to web api are working well.
c# port number 63213 , client at 8080
I also using vuex and i am wonder if I should connect in at the store.
here are code examples. I use vue/vuex with typescript falvor.
mounted: function() {
//... under mounted, signalR connection. i am using import * as signalR from "#aspnet/signalr";
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl("http://localhost:63213/ChatHub")
.build();
// connecting to the hub
this.hubConnection
.start()
.then(() => console.log("connection started"))
.catch(err => console.log("connecting hub failed err is : ", err));
//at the hub there is a function named broadcastMessage, should return string that will be added to an array. should it be at sotr's getter
this.connection.on("broadcastMessage", function(msg: string) {
this.messages.push({ msg });
});
},
c#
public class Startup
{
public void Configuration(IAppBuilder app)
{
var policy = new CorsPolicy()
{
AllowAnyOrigin = true,
AllowAnyHeader = true,
AllowAnyMethod = true,
SupportsCredentials = true
};
policy.Origins.Add("http://localhost:8080");
// Any connection or hub wire up and configuration should go here
app.MapSignalR();
}
}
pot get to web api are working well.
hub
public class ChatHub : Hub
{
public static void SendMessage(string msg)
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
hubContext.Clients.All.broadcastMessage(msg, " !! !! ");
}
}
error is:
Access to XMLHttpRequest at 'http://localhost:63213/ChatHub/negotiate' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
should i pass the hub connention to the store?
what am i doing wrong?
thank you.
switched to .core object.
under "Configure"
app.UseCors(builder => builder.WithOrigins("http://localhost:8080").AllowAnyMethod().AllowAnyHeader().AllowCredentials());
app.UseSignalR(route => {route.MapHub<UserHub>("/user-hub");} );
under
ConfigureServices
services.AddSignalR();
services.AddCors();
at vue component (ts)
created: function() {
this.$userHub.$on("user-added-event", this.userAddedEvent);
},
beforeDestroy: function() {
//clean SignalR event
this.$userHub.$off("user-added-event", this.userAddedEvent);
},
user-hub.js used to handle connection.
imported as vue plugin
import { HubConnectionBuilder, LogLevel } from "#aspnet/signalr";
export default {
install(Vue) {
const connection = new HubConnectionBuilder()
.withUrl(`${Vue.prototype.$http.defaults.baseURL}/user-hub`)
.configureLogging(LogLevel.Information)
.build();
const userHub = new Vue();
Vue.prototype.$userHub = userHub;
connection.on("AddUserEvent", (userId, userName) => {
userHub.$emit("user-added-event", { userId, userName });
});
// if connection closed, reopen it
let startedPromise = null;
function start() {
startedPromise = connection.start().catch(err => {
return new Promise((resolve, reject) =>
setTimeout(
() =>
start()
.then(resolve)
.catch(reject),
5000
)
);
});
return startedPromise;
}
connection.onclose(() => start());
start();
}
};
full project will be uploaded to git.