Search code examples
c#asp.net-coresignalraccess-tokensignalr-hub

HubConnection Authentication Using Identity From Windows Service


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.


Solution

  • 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