I have a Hub that serves up near real-time data. I want the provider of the real-time data to be able to send messages to all connected clients. The problem I am running into is authenticating the connection because the provider is a Windows Service. So, I am creating a client using HubConnectionBuilder
. When I attempt to connect, I am getting a 401 authentication failure which is expected.
I am trying to figure out how to use the existing Identity authentication that exists in the shared Entity Framework database. Is there a way to get an authentication token using UserManager or SigninManager from this stand-alone service? Or is there a better approach to allow the HubConnection to authenticate?
Here is the connection code I am using:
var notifyConnection = new HubConnectionBuilder()
.WithUrl("https://localhost:5001/notify", options =>
{
options.AccessTokenProvider = () => Task.FromResult(_myAccessToken);
})
.WithAutomaticReconnect()
.Build();
notifyConnection.Closed = async (error) =>
{
if (Debug) _logger.LogInformation("Hub connection closed: " error.Message);
await Task.Delay(new Random().Next(0, 5) * 1000);
await notifyConnection.StartAsync();
};
try
{
var task = notifyConnection.StartAsync();
task.Wait();
}
catch (Exception ex)
{
if (Debug) _logger.LogInformation("Hub connection start error: " ex.Message);
}
_myAccessToken
is not defined. I am trying to figure out how to get a valid access token that will authenticate properly.
EDIT:
I have tried adding JWT authentication. I can verify that the bearer token is getting generated properly. But it doesn't appear to be validated on the Hub
.
I have added the following to the services configuration:
var key = new SymmetricSecurityKey(System.Text.Encoding.ASCII.GetBytes(Configuration["JwtKey"]));
services.AddAuthentication().AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
LifetimeValidator = (before, expires, token, parameters) =>
expires > DateTime.UtcNow,
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
ValidateLifetime = true,
IssuerSigningKey = key,
NameClaimType = ClaimTypes.NameIdentifier
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(accessToken))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
services.AddAuthorization(options =>
{
options.FallbackPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
{
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireClaim(ClaimTypes.NameIdentifier);
});
});
I have also updated the HubConnectionBuilder
to use:
notifyConnection = new HubConnectionBuilder()
.WithUrl(baseUrl "/notify", options =>
{
options.AccessTokenProvider = async () =>
{
var stringData = JsonConvert.SerializeObject(new { username = "****", password = "****" });
var content = new StringContent(stringData);
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = await httpClient.PostAsync(baseUrl "/api/token", content);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
};
})
.WithAutomaticReconnect()
.Build();
It still returns a 401. When I tried to force the Authorize
attribute to:
[Authorize(AuthenticationSchemes = "Bearer,Cookies")]
It then returns a 500 error.
CodePudding user response:
So, after changing the Authorize
attribute on the Hub class, I was able to get JWT authentication working along with the built-in Identity authentication.
Here is the new Authorize
attribute:
[Authorize(AuthenticationSchemes = "Bearer,Identity.Application")]
I added a simple token generation API controller to feed the HubConnection. The following sources were very helpful:
https://www.codemag.com/article/1807061/Build-Real-time-Applications-with-ASP.NET-Core-SignalR
.Net SignalR use JWT Bearer Authentication when Cookie Authentication is also configured