Our Asp.net core/React application used SignalR with good performance.
We added an authentication, made client side (React), with msal.
Since I added the AzureAD authentication, the negotiation of SignalR connection between client and server goes from WebSockets -> ServerSideEvents -> Long polling
With Long polling the performance degrades enormously.
In connection phase, there is a 401, despite the Jwt being passed both as query string and header
Here is the client code, passing the token:
let optimizerConnection = new HubConnectionBuilder()
.withUrl("/hubpath",
{
accessTokenFactory: () => getRawJwtToken() },
false, // skipNegotiation
HttpTransportType.WebSockets //transport
)
.withAutomaticReconnect()
.configureLogging(LogLevel.Trace)
.build()
Here is the the Hub with required authentication (the identity information, claims,...) goes to the server with the [Authorize] attribute. But authentication makes to use long polling, and is so slow:
[Authorize(AuthenticationSchemes=JwtBearerDefaults.AuthenticationScheme, Roles="Optimizer")]
public class SomeHub : Hub, ISomeHub { ...}
Here is the configuration of the identity server side:
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(configuration);
I read all the posts I found about the subject, sometimes with Jwt authentication (not AzureAD). Some posts mention an OnMessageReceived method. I implemented OnMessageReceived, but it is not called:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(ConfigureJwtBearerOptions,
ConfigureMicrosoftIdentityOptions,
jwtBearerScheme: "Bearer",
subscribeToJwtBearerMiddlewareDiagnosticsEvents: true);
private static void ConfigureMicrosoftIdentityOptions(MicrosoftIdentityOptions options)
{
options.Authority = "https://login.microsoftonline.com";
options.ClientId = "myClientIdGuid";
options.Instance = "https://login.microsoftonline.com/myInstanceGuid";
}
private static void ConfigureJwtBearerOptions(JwtBearerOptions options)
{
options.Events = new JwtBearerEvents
{
OnChallenge = OnChallenge,
OnTokenValidated = OnTokenValidated,
OnAuthenticationFailed = OnAuthenticationFailed,
OnForbidden = OnForbidden,
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(SomeHub.Path)))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
}
If anybody has a clue, thank you
I got it working ! Even if I didn't get the OnMessageReceived to be called.
A modification was adding a service - likely to be closely equivalent to AddAuthentication() + AddMicrosoftIdentityWeabApi()
services.AddMicrosoftIdentityWebApiAuthentication(configuration, "AzureAd");
And then add a custom middleware before UseAuthentication()
app.UseMiddleware<AccessTokenMiddleware>();
app.UseAuthentication();
The middleware puts the query string parameter in the headers
public class AccessTokenMiddleware
{
private readonly RequestDelegate next;
public AccessTokenMiddleware(RequestDelegate next)
{
this.next = next;
}
public async Task Invoke(HttpContext httpContext)
{
var request = httpContext.Request;
// Web sockets cannot pass headers so the access token must be taken from query param and
// added to the header before authentication middleware runs
if (request.Path.StartsWithSegments(SomeHub.Path, StringComparison.OrdinalIgnoreCase) &&
request.Query.TryGetValue("access_token", out var accessToken))
request.Headers.Add("Authorization", $"Bearer {accessToken}");
await next(httpContext);
}
}