($width) AND $width .= 'px'; $style = " style=\"width: $width\""; } $value = $value ? $value : date('H:i'); $s = ""; return $s; } // form_date('start', '2018-07-05') 为空则当前日期 function form_date($name, $value = 0, $width = FALSE) { $style = ''; if (FALSE !== $width) { is_numeric($width) AND $width .= 'px'; $style = " style=\"width: $width\""; } $value = $value ? $value : date('Y-m-d'); $s = ""; return $s; } /**用法 * * echo form_radio_yes_no('radio1', 0); * echo form_checkbox('aaa', array('无', '有'), 0); * * echo form_radio_yes_no('aaa', 0); * echo form_radio('aaa', array('无', '有'), 0); * echo form_radio('aaa', array('a'=>'aaa', 'b'=>'bbb', 'c'=>'ccc', ), 'b'); * * echo form_select('aaa', array('a'=>'aaa', 'b'=>'bbb', 'c'=>'ccc', ), 'a'); */ ?>asp.net core - Azure SignalR - Client connection fails with 401-Unauthorized 'invalid_token. The signature key was not f
最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

asp.net core - Azure SignalR - Client connection fails with 401-Unauthorized 'invalid_token. The signature key was not f

programmeradmin1浏览0评论

Question Clarification: The examples Microsoft provides and feedback I have received assume that the client connection and Hub code are in the same application running ASP.NET Core.

My Hub code is a separate application running in ASP.NET Core. The clients are in another application running in ASP.NET. They are not combined.


I had a working local SignalR service that I need to integrate with Azure SignalR Service (to manage connections).

The clients are Javascript. The server-side hub code is ASP.NET Core (v9).

The hub connects to the signalr service with no problems and I see the connections in Live Trace Tool.

The client fetches a JWT token and attempts to connect to signalr service. The attempt appears in Live Trace Tool. It fails with a 401 - Unauthorized. DevTools shows the specific error in Www-Authenticate as "Bearer error='invalid_token', error_description='The signature key was not found'.

I verified the JWT signature key is present in the Hub code and that it matches the key used to generate the token.

Server-Side Hub Code (program.cs)


var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("AzureSignalRConnectionString");

//builder.Services.AddCors();
builder.Services.AddSignalR().AddAzureSignalR(connectionString);

builder.Services.AddAuthorization(options => {
    options.AddPolicy("AuthenticatedUsers", policy => {
        policy.RequireAuthenticatedUser();
    });
});

builder.Services.AddAuthentication(options => {
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
    .AddJwtBearer(options => {
        options.TokenValidationParameters = new TokenValidationParameters {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "lobbycentral",
            ValidAudience = ";,
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["TokenKey"]))
        };

        options.Events = new JwtBearerEvents {
            OnMessageReceived = context => {
                var token = context.Request.Query["token"];
                var path = context.HttpContext.Request.Path;

                if (!string.IsNullOrEmpty(token) && path.StartsWithSegments("/hub")) {
                    context.Token = token;
                }
                return Task.CompletedTask;

            }
        };
    });

builder.Services.AddAuthorization();

builder.Services.AddCors(options => options.AddPolicy("testing", policy => {
    policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin();
}));

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors("testing");
app.UseAuthorization();
app.MapHub<NotificationHub>("NotificationHub");
app.Run();

JWT Token API (localhost)

    var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(TOKEN_KEY));
    var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

var clientID = "user123";
    var issuer = "<my issuer>";
    var audience = "https://<resource_name>.service.signalr";

    var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, clientID),
new Claim(JwtRegisteredClaimNames.Iss, issuer),
new Claim(JwtRegisteredClaimNames.Aud, audience),
new Claim(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddHours(1).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
new Claim(JwtRegisteredClaimNames.Nbf, DateTimeOffset.UtcNow.AddSeconds(-1).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
new Claim("role", "client"),

    };

    var token = new JwtSecurityToken(
        issuer: issuer,
        audience: audience,
        claims: claims,
        expires: DateTime.UtcNow.AddHours(1),
        signingCredentials: credentials
    );

    return new JwtSecurityTokenHandler().WriteToken(token);

Javascript Client (index.html)

    <script src=".0.1/signalr.js"></script>

<script type='text/javascript'>

var URL = 'https://<resource_name>.service.signalr/client/?hub=NotificationHub';

notifyConnection = new signalR.HubConnectionBuilder()
    .withUrl(URL, {
        accessTokenFactory: async () => {
            const response = await fetch("https://localhost:44328/v1/Authenticate/GetSRToken?companyID=123&clientID=user123");
            const data = await response.json();
            console.debug('token is ' + data.token);
            return data.token;
        }
    })
    .withAutomaticReconnect()
    .configureLogging(signalR.LogLevel.Information)
    .build();

console.debug("starting connection");

notifyConnection.start();

</script>

What I've checked:

  1. Verified that all required claims are present in the JWT token using jwt.io.
  2. Verified that the Signature Key on the server-side hub and token generator match.
  3. Verified the token is being generated and was present in the Bearer header.
  4. Added CORS policy.

Azure SignalR Instance details:

  1. Cors is set to '*'
  2. Pricing tier is 'Free'
  3. Service Mode is 'Default'

Question Clarification: The examples Microsoft provides and feedback I have received assume that the client connection and Hub code are in the same application running ASP.NET Core.

My Hub code is a separate application running in ASP.NET Core. The clients are in another application running in ASP.NET. They are not combined.


I had a working local SignalR service that I need to integrate with Azure SignalR Service (to manage connections).

The clients are Javascript. The server-side hub code is ASP.NET Core (v9).

The hub connects to the signalr service with no problems and I see the connections in Live Trace Tool.

The client fetches a JWT token and attempts to connect to signalr service. The attempt appears in Live Trace Tool. It fails with a 401 - Unauthorized. DevTools shows the specific error in Www-Authenticate as "Bearer error='invalid_token', error_description='The signature key was not found'.

I verified the JWT signature key is present in the Hub code and that it matches the key used to generate the token.

Server-Side Hub Code (program.cs)


var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("AzureSignalRConnectionString");

//builder.Services.AddCors();
builder.Services.AddSignalR().AddAzureSignalR(connectionString);

builder.Services.AddAuthorization(options => {
    options.AddPolicy("AuthenticatedUsers", policy => {
        policy.RequireAuthenticatedUser();
    });
});

builder.Services.AddAuthentication(options => {
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
    .AddJwtBearer(options => {
        options.TokenValidationParameters = new TokenValidationParameters {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "lobbycentral",
            ValidAudience = "https://lobbycentral.service.signalr",
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["TokenKey"]))
        };

        options.Events = new JwtBearerEvents {
            OnMessageReceived = context => {
                var token = context.Request.Query["token"];
                var path = context.HttpContext.Request.Path;

                if (!string.IsNullOrEmpty(token) && path.StartsWithSegments("/hub")) {
                    context.Token = token;
                }
                return Task.CompletedTask;

            }
        };
    });

builder.Services.AddAuthorization();

builder.Services.AddCors(options => options.AddPolicy("testing", policy => {
    policy.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin();
}));

var app = builder.Build();

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseCors("testing");
app.UseAuthorization();
app.MapHub<NotificationHub>("NotificationHub");
app.Run();

JWT Token API (localhost)

    var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(TOKEN_KEY));
    var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

var clientID = "user123";
    var issuer = "<my issuer>";
    var audience = "https://<resource_name>.service.signalr";

    var claims = new[] {
new Claim(JwtRegisteredClaimNames.Sub, clientID),
new Claim(JwtRegisteredClaimNames.Iss, issuer),
new Claim(JwtRegisteredClaimNames.Aud, audience),
new Claim(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddHours(1).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
new Claim(JwtRegisteredClaimNames.Nbf, DateTimeOffset.UtcNow.AddSeconds(-1).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
new Claim("role", "client"),

    };

    var token = new JwtSecurityToken(
        issuer: issuer,
        audience: audience,
        claims: claims,
        expires: DateTime.UtcNow.AddHours(1),
        signingCredentials: credentials
    );

    return new JwtSecurityTokenHandler().WriteToken(token);

Javascript Client (index.html)

    <script src="https://cdnjs.cloudflare/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script>

<script type='text/javascript'>

var URL = 'https://<resource_name>.service.signalr/client/?hub=NotificationHub';

notifyConnection = new signalR.HubConnectionBuilder()
    .withUrl(URL, {
        accessTokenFactory: async () => {
            const response = await fetch("https://localhost:44328/v1/Authenticate/GetSRToken?companyID=123&clientID=user123");
            const data = await response.json();
            console.debug('token is ' + data.token);
            return data.token;
        }
    })
    .withAutomaticReconnect()
    .configureLogging(signalR.LogLevel.Information)
    .build();

console.debug("starting connection");

notifyConnection.start();

</script>

What I've checked:

  1. Verified that all required claims are present in the JWT token using jwt.io.
  2. Verified that the Signature Key on the server-side hub and token generator match.
  3. Verified the token is being generated and was present in the Bearer header.
  4. Added CORS policy.

Azure SignalR Instance details:

  1. Cors is set to '*'
  2. Pricing tier is 'Free'
  3. Service Mode is 'Default'
Share Improve this question edited Feb 3 at 17:25 Jaime Jesús asked Jan 29 at 19:49 Jaime JesúsJaime Jesús 1014 bronze badges
Add a comment  | 

2 Answers 2

Reset to default 0

There are 3 issues in your project.

The first, you are missing app.UseAuthentication(); , you should add it like below.

app.UseAuthentication();
app.UseAuthorization();

The second, you are using custom jwt scheme to protect the signalr connection, so you should define it like below in Program.cs file. For more details you can check this thread.

// add below settings for signalr
.AddJwtBearer("SignalRJwtScheme", options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ....
    }
}

And add attribute in your hub like:

[Authorize(AuthenticationSchemes = "SignalRJwtScheme")]
public class YourHub : Hub
...

The third one is you are using var token = context.Request.Query["token"];. We will use access_token by default. You can double-check it, if yours is correct, you can ignore this suggestion.

The Microsoft documentation here states that clients connect directly to the service instead of the app server.

However, it left off the fact that you must first connect to your app server to negotiate. After that, the connection is handed off to the SignalR service.

This is what Copilot had to say when I asked why the article says clients connect to the service:

That's a great observation. The article is not misleading; rather, it emphasizes the final connection state while omitting some implementation details. Let me clarify:

Key Point: The client does ultimately connect directly to the Azure SignalR service for real-time messaging. However, the negotiation process involves the app server.

Here's a more detailed breakdown:

  1. Initial Connection to App Server: The client first makes a request to the app server to initiate the connection. This step is necessary for the app server to perform any required authentication, authorization, and to provide the client with the necessary connection details.

  2. Negotiation: The app server handles the negotiation process. It communicates with the Azure SignalR service to get a connection token and the endpoint information. The app server then provides this information to the client.

  3. Direct Connection to SignalR Service: After receiving the connection details from the app server, the client uses this information to connect directly to the Azure SignalR service for the actual real-time communication.

So, while the negotiation starts with the app server, the actual messaging happens directly between the client and the Azure SignalR service. The article is correct in stating that clients connect directly to the Azure SignalR service, but the initial negotiation step involving the app server is also a necessary part of the process.

After I made the change to the client, everything worked. I also verified in Azure portal that there is one client connection.

var URL = 'https://<resource_name>.service.signalr/client/?hub=NotificationHub';

Changed to:
var URL = 'https://localhost/NotificationHub

与本文相关的文章

发布评论