Reputation: 661
I have a REST API (core 2.1) which need to support a SPA, providing restful endpoints and some live-interactive features using SignalR;
The Hub/MVC routes are running on the same server, which is also the provider of the JWT token.
After loggin in, the client-side receives a JWT token, which places on the header for every REST request, otherwise it gets a 401 (This is working with the [Authorize]
attribute).
At the client-side, the code below tries to connect to my /hub endpoint:
new HubConnectionBuilder().withUrl(HUB_URL, { accessTokenFactory: () => this.getToken() })
And if I place [Authorize]
at my Hub class, I get the following error (Without the authorization, the client can send and listen correctly):
WebSocket connection to 'wss://localhost:5001/hub?id=MY_ID&access_token=MY_TOKEN' failed: HTTP Authentication failed; no valid credentials available
The server logged failed authentications:
(Triggered a console.log on AddJwtBearerOptions.Events.OnMessageReceived)
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET https://localhost:5001/hub? id=MY_ID&access_token=MY_TOKEN
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2]
Authorization failed.
info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[12]
AuthenticationScheme: Bearer was challenged.
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 0.3658ms 401
Differently from the those requests, using the SAME JWT TOKEN with the REST ones (Using [Authorize]
), with the Header: Bearer XXXX
instead of querystring, triggers the OnTokenValidated
. The OnAuthenticationFailed
is never triggered, even if the authentication fails:
(Triggered a console.log on AddJwtBearerOptions.Events.OnMessageReceived)
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET https://localhost:5001/api/products application/json
(Triggered a console.log on AddJwtBearerOptions.Events.OnTokenValidated)
info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[2]
Successfully validated the token.
ConfigureServices(IServiceCollection)
services.AddSignalR();
services.AddCors(option => option.AddPolicy("CorsPolicy", p => p.AllowAnyHeader().AllowAnyOrigin().AllowAnyMethod().AllowCredentials()));
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters{
ValidateIssuer = true,
ValidIssuer = Configuration["JWT:Issuer"],
ValidateLifetime = true,
ValidateAudience = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JWT:SecurityKey"]))
};
});
Configure(IApplicationBuilder)
app.UseAuthentication();
app.UseCors("CorsPolicy");
app.UseHttpsRedirection();
app.UseMvc();
app.UseSignalR(routes => {
routes.MapHub<ApplicationHub>("/hub");
});
Upvotes: 10
Views: 9491
Reputation: 1243
To expand on what @marcusturewicz
already have stated, one should also check for the token inside the Authorization
header in case if the access token is sent in the header instead of query.
Although, this may vary from how e.g., the client implementer sends the token.
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// If no token is present in the query, we can
// try to get it from the Authorization header.
StringValues accessToken = context.Request.Query["access_token"] == StringValues.Empty
? context.Request.Headers["Authorization"]
: context.Request.Query["access_token"];
// If the request is for our hub...
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) &&
(path.StartsWithSegments("/hub")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
Upvotes: 0
Reputation: 524
info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] Authorization failed. info: Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler[12] AuthenticationScheme: Bearer was challenged. info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2] Request finished in 0.3658ms 401
Spend 20 hours for fixing. :( Reason was in:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class ChatHub : Hub<IChatClient>
My service configuration:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options => ...)
UPD: Full work example with JS client here.
Upvotes: 7
Reputation: 2494
You also need to add this block inside the .AddJwtBearer
section:
// We have to hook the OnMessageReceived event in order to
// allow the JWT authentication handler to read the access
// token from the query string when a WebSocket or
// Server-Sent Events request comes in.
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("/hub")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
This can be found here in the docs.
Upvotes: 14