I'm trying to implement SignalR support in an ASP.NET / Angular App, but I'm having difficulties with the JWT-Authentication.
First I'm appending the token in the appropriate request, which can be seen in the server code down below.
This seems to work fine, but I always get HTTP error 401 (Unauthorized) when trying to establish the websocket connection. When hitting a breakpoint in the code above, I can see that the token is assigned correctly:
All the other HTTP endpoints have the [Authorize] annotation, where the User object is accessible as excepted.
Here's a screenshot of the HTTP traffic. It's the same bearer token that works for all the other endpoints, but here it says "invalid token":
The server uses the Microsoft.AspNetCore.SignalR package, Version 1.1.0
Angular Client uses "#microsoft/signalr": "^5.0.7"
Here's the client code where the token is added when establishing the connection:
public startConnection = () => {
const options: IHttpConnectionOptions = {
accessTokenFactory: () => {
return this._settings.authorization?.access_token;
}
};
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl('http://localhost:8990/apphub', options)
.build();
this.hubConnection
.start()
.then(() => console.log('Connection started'))
.catch(err => console.log('Error while starting connection: ' + err))
}
This is the startup code to add JWT authentication:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnTokenValidated = ctx =>
{
Log.Logger.Information("Authentication - Token validated.");
return Task.CompletedTask;
},
OnMessageReceived = ctx =>
{
Log.Logger.Information("Authentication - Message received.");
var tokenInQuery = ctx.Request.Query.TryGetValue("authorization", out var queryToken);
var tokenInHeader = ctx.Request.Headers.TryGetValue("authorization", out var headerToken);
if (tokenInQuery || tokenInHeader)
{
var path = ctx.HttpContext.Request.Path;
if (path.StartsWithSegments("/apphub", StringComparison.OrdinalIgnoreCase))
{
if(!string.IsNullOrEmpty(queryToken))
{
ctx.Token = queryToken;
}
else if(!string.IsNullOrEmpty(headerToken))
{
ctx.Token = headerToken;
}
}
}
return Task.CompletedTask;
},
OnAuthenticationFailed = ctx =>
{
Log.Logger.Information("Authentication - Authentication failed.");
return Task.CompletedTask;
}
};
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["SecurityKeyIssuer"],
ValidAudience = Configuration["SecurityKeyAudience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["SecurityKey"]))
};
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddMemoryCache();
// services.AddCors();
services.AddCors(o => o.AddPolicy("AllowAnyOrigin", builder =>
{
builder.WithOrigins("http://locahost:4000")
//.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.SetIsOriginAllowed((x) => true)
.AllowCredentials();
}));
Any ideas?
Related
I'm using SignalR and angular to create a chat between clients, after the client logged in successfully with jwt token. Once I've added -
[Authorize]
to my hub, I'm getting this error while trying to connect to SignalR -
Debug: HubConnection failed to start successfully because of error 'Error: Failed to complete negotiation with the server: Error: : Status code '401''.
Before I added this attribute, my app connected successfully to SignalR so I know the problem is authorization. What am I doing wrong?
UserHub-
[Authorize]
public class UserHub : Hub
Program.cs-
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder => builder
.WithOrigins("http://localhost:4200")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.SetIsOriginAllowed((host) => true));
});
builder.Services.AddDbContext<TalkBackDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("TalkBackConnectionString")));
builder.Services.AddScoped<IContactRepository, ContactsRepository>();
builder.Services.AddScoped<IWebAPIService, WebAPIService>();
builder.Services.AddScoped<ISignalrService, SignalrService>();
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddSignalR();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = false,
ValidateAudience = false,
ValidAudience = builder.Configuration["Jwt:Audience"],
ValidIssuer = builder.Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && (path.StartsWithSegments("/user")))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
Client-
public startSignalrConnection(connectionUrl: any) {
return new Promise<any>((resolve, reject) => {
this.hubConnection = new HubConnectionBuilder()
.withUrl(connectionUrl, {
withCredentials: false,
accessTokenFactory: () => localStorage.getItem('jwt')!,
})
.configureLogging(LogLevel.Debug)
.build();
After many tries, I found out what was the problem.
I missed this line on Program.cs -
app.UseAuthentication();
I've also edited my token (on another microservice) to be the same like here, and added this line -
ValidateIssuerSigningKey=true
Now I can access to SignalR.
I am facing an issue that /nagotiate request fails with 401(Unauthorized).
Server
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR(options => {
options.EnableDetailedErrors = true;
})
.AddAzureSignalR(options =>
{
options.InitialHubServerConnectionCount = 1;
options.ConnectionString = "xxxx"
});
}
protected virtual void ConfigureAuthentication(IServiceCollection services)
{
services
.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = $"{AzureB2CConfig.Instance}/{AzureB2CConfig.Domain}/{AzureB2CConfig.SignUpSignInPolicyId}/v2.0/";
options.Audience = AzureB2CConfig.Audience;
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var authToken = context.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(authToken) &&
(path.StartsWithSegments("/myhubs")))
{
context.Token = authToken;
}
return Task.CompletedTask;
}
};
options.TokenValidationParameters =
new TokenValidationParameters
{
LifetimeValidator = (before, expires, token, param) =>
{
return expires > DateTime.UtcNow;
},
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
ValidateLifetime = true,
ValidateIssuerSigningKey = false
};
});
services.AddAuthorization(options =>
{
options.AddPolicy("AllowedUser", policy =>
{
policy.Requirements.Add(new AllowedUserRequirement());
});
});
}
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseAuthorization();
app.UseFileServer();
app.UseEndpoints(routes =>
{
routes.MapHub<MyHub>($"/myhubs");
});
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Client
var connection = new HubConnectionBuilder()
.WithUrl(SignalRServerUrl, options =>
{
options.AccessTokenProvider = authenticationService.GetAccessToken; // returns valid access token
})
.WithAutomaticReconnect()
.Build();
await Policy
.Handle<Exception>()
.WaitAndRetryAsync(1, x => TimeSpan.FromMilliseconds(500))
.ExecuteAsync(() => _connection.StartAsync()); // causes 401 Unauthorized
I have tried many things but none of them helped.
I have read a lots of articles and here is example. this, this, this, this, this, this,this, this and have spend few days for finding the solution but could not..
Are there any missing configuration?
FrameWork: .Net6
Updated at 20th Oct 2022
I Added services.AddAuthorization() parts and Authentication is now success between the app and the server. But app connects to Azure SignalR. This Azure SignalR authentication is failing. 401
Problem
I have a Target Framework: .NET 6.0 API backend with an exposed API with Identity Framework implemented in it.
I can successfully obtain data 0 issue on unauthorized API Endpoints with this above them on both SwaggerUI AND my localhost frontend. [AllowAnonymous]
When it comes to authorized API Endpoints however it is a totally different story.
SwaggerUI has NO PROBLEM AT ALL when I put my generated "JWT" into the "Authorize" padlock on the side of the screen.
Localhost JWT are simply not accepted or seen or understood or something I totally don't understand.
Request seems fine
Screenshot of the API request I am sending over has the authorization with the token, I also took the token to JWT io and it can parse out the name without problem.
I've also tried removing or adding "bearer" to the authorization header sent over and that makes no difference.
Screencap of request:
https://i.imgur.com/CY7F32o.png
My entire Startup.cs
(I know showing anything less would make it harder for you all to help me so >_< )
namespace API
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DbContext>(opt =>
{
opt.UseLazyLoadingProxies();
opt.UseMySql(Configuration.GetConnectionString("DefaultConnection"), ServerVersion.AutoDetect(Configuration.GetConnectionString("DefaultConnection")), opt => opt.EnableRetryOnFailure());
});
services.AddIdentity<UserModel, IdentityRole>(options =>
{
//options.Password.RequiredLength = 5;
//options.Password.RequireLowercase
options.Lockout.MaxFailedAccessAttempts = 5;
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
options.User.RequireUniqueEmail = true;
})
.AddEntityFrameworkStores<DbContext>()
.AddDefaultTokenProviders();
services.Configure<CookiePolicyOptions>(options =>
{
options.MinimumSameSitePolicy = SameSiteMode.None;
options.Secure = CookieSecurePolicy.Always;
});
services.ConfigureApplicationCookie(options =>
{
options.Cookie.Name = "auth_cookie";
options.Cookie.SameSite = SameSiteMode.None;
options.LoginPath = new PathString("/api/contests");
options.AccessDeniedPath = new PathString("/api/contests");
options.Events.OnRedirectToLogin = context =>
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.CompletedTask;
};
});
services.Configure<SmtpSettings>(Configuration.GetSection("SMTP"));
services.AddSingleton<IEmailManager, EmailManager>();
services.AddSingleton<IAuthorizationPolicyProvider, PermissionPolicyProvider>();
services.AddScoped<IAuthorizationHandler, PermissionAuthorizationHandler>();
services.AddControllers(opt =>
{
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
opt.Filters.Add(new AuthorizeFilter(policy));
});
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new() { Title = "API", Version = "v1" });
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please insert JWT with Bearer into field",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
});
//Allow localhost to actually contact the server.
services.AddCors(opt =>
{
opt.AddPolicy("CorsPolicy", policy =>
{
policy.AllowAnyHeader()
.AllowAnyMethod()
.WithExposedHeaders("WWW-Authenticate")
.WithOrigins("http://localhost:3000", "http://localhost:5000", "https://localhost:5000")
.AllowCredentials();
});
});
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["TokenKey"]));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(opt =>
{
//"Normal" API Auth.
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidateAudience = false,
ValidateIssuer = false,
ValidateLifetime = true, //~5 minute leeway?
ClockSkew = TimeSpan.Zero //force time.
};
//SignalR auth.
opt.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
var path = context.HttpContext.Request.Path;
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/chat"))
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
}).AddDiscord(options =>
{
options.CorrelationCookie.SameSite = SameSiteMode.Lax;
options.ClientId = Configuration["Discord:ClientId"];
options.ClientSecret = Configuration["Discord:ClientSecret"];
options.Scope.Add("email");
//options.CallbackPath = "/";
});
services.AddScoped<IJwtGenerator, JWTGenerator>();
services.AddScoped<IUserAccessor, UserAccessor>();
services.AddScoped<IImageAccessor, ImageAccessor>();
services.Configure<Infrastructure.Images.CloudinarySettings>(Configuration.GetSection("Cloudinary"));
services.AddRazorPages().AddRazorRuntimeCompilation();
services.AddServiceLayer();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseDeveloperExceptionPage();
app.UseMiddleware<ErrorHandlingMiddleware>();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "API v1");
c.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.None);
});
app.UseCors(c => c.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseCors("CorsPolicy");
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapFallbackToController("Index", "Fallback");
});
}
}
}
If I use http calls outside of signalr, such as with postman or httpclient, I am able to have my token validated successfully on the server. It's when I try to connect through my signalr hub that the token is not passing authorization.
Bearer was not authenticated. Failure message: No SecurityTokenValidator available for token: Bearer MyTokenFooBar
My service setup is:
public void ConfigureServices(IServiceCollection services)
{
services.AddRouting();
services.AddControllers();
services.AddHealthChecks();
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(builder => { builder.ConnectionString = _configuration.GetConnectionString("DefaultConnection"); }));
services.AddIdentity<ApplicationUser, IdentityRole>(setup =>
{
// foo
}).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = _configuration["Jwt:Issuer"],
ValidAudience = _configuration["Jwt:Audience"],
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["Jwt:Key"])),
ValidateLifetime = false
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var path = context.HttpContext.Request.Path;
if (!path.StartsWithSegments("/chat")) return Task.CompletedTask;
var accessToken = context.Request.Headers[HeaderNames.Authorization];
if (!string.IsNullOrWhiteSpace(accessToken) && context.Scheme.Name == JwtBearerDefaults.AuthenticationScheme)
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
services.AddAuthorization();
services.AddSignalR(options => { options.EnableDetailedErrors = true; });
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(options =>
{
options.MapHealthChecks("/health");
options.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");
});
app.UseSignalR(options => { options.MapHub<ChatHub>("/chat"); });
}
I use a basic http auth header for the initial connection, which will sign the user into identity and generate a jwt token as a response for use in future calls.
[HttpPost]
[AllowAnonymous]
public async Task<IActionResult> Login()
{
var (headerUserName, headerPassword) = GetAuthLoginInformation(HttpContext);
var signInResult = await _signInManager.PasswordSignInAsync(headerUserName, headerPassword, false, false);
if (!signInResult.Succeeded)
{
return Unauthorized();
}
var signingKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SuperTopSecretKeyThatYouDoNotGiveOutEver!"));
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);
var jwt = new JwtSecurityToken(signingCredentials: signingCredentials);
var handler = new JwtSecurityTokenHandler();
var token = handler.WriteToken(jwt);
return new OkObjectResult(token);
}
And my client (a console application) is setup to cache this token and use it in future signalr calls as such:
Get the token:
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(encoding.GetBytes($"{userName}:{password}")));
var response = await _client.SendAsync(request); // this goes to the login action posted above
_token = await response.Content.ReadAsStringAsync();
...
_hubConnection = new HubConnectionBuilder()
.WithUrl(new Uri(_baseAddress, "chat"),
options => { options.AccessTokenProvider = () => Task.FromResult(_token); }) // send the cached token back with every request
.Build();
// here is where the error occurs. 401 unauthorized comes back from this call.
await _hubConnection.StartAsync();
Resolved.
The issue was that I was overriding the OnMessageReceived handler of the JwtBearerHandler and then having it read the incoming token myself... but the token I was passing it included the prefix Bearer, which when parsed by the above handler did not match the known token for the existing user.
Simply removing my override of OnMessageReceived and letting AspNetCore's deafult implementation of the JwtBearerHandler do its job allowed the token parsing to work correctly.
I made an asp.net core 2.0 SignalR Hub which uses Bearer Token for Authentication. Now I'm a bit lost on how to connect to it via the SignalR Angular 5 client. I actually can connect if I remove authorization from the Hub, so the connection is working, now I believe I just need to add the Authorization Bearer to the Http Headers of the connection.
The SignalR client reference in the package.json file of my Angular 5 project: "#aspnet/signalr-client": "^1.0.0-alpha2-final"
My Angular component:
import { Component, OnInit } from '#angular/core';
import { finalize } from 'rxjs/operators';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { ToastrService } from 'ngx-toastr';
import { AuthenticationService } from '../core/authentication/authentication.service';
import { HubConnection } from '#aspnet/signalr-client';
#Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
quote: string;
isLoading: boolean;
jwtToken:string;
private hubConnection: HubConnection;
constructor(
private _http: HttpClient,
private _auth : AuthenticationService,
private _toastr: ToastrService) { }
ngOnInit() {
this.isLoading = false;
this.jwtToken = this._auth.currentToken;
this.hubConnection = new HubConnection('http://localhost:27081/hub/notification/');
this.hubConnection
.start()
.then(() => console.log('Connection started!'))
.catch(err => console.error('Error while establishing connection :(', err));
this.hubConnection.on("send", data => {
console.log(data);
});
}
showToastr(){
this._toastr.success('Hello world!', 'Toastr fun!');
}
}
Due to reading similar questions I tried: this.hubConnection.Headers.Add("token", tokenValue); but it doesn't work, the Headers property doesn't exist.
How can I add the Bearer token to the Http Headers of the HubConnection?
Thanks for any help
To do this with #aspnet/signalr (^1.1.4) you can use the following code
const options: IHttpConnectionOptions = {
accessTokenFactory: () => {
return "Token is resolved here";
}
};
const connection = new signalR.HubConnectionBuilder()
.configureLogging(signalR.LogLevel.Information)
.withUrl(`${environment.apiUrl}/notify`, options)
.build();
Also add an annotation to your Hub
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
As a side note, SignalR when using the websocket protocol does not seem to attach the Bearer token as a header and instead adds it to the request URL as an 'access_token' parameter, this requires you to configure your authentication to handle this token when signalR chooses to use ws.
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false
};
x.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("/notify")))
{
// Read the token out of the query string
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
From reading their source code and tests, it looks like you can provide an options object containing your access token, like so
var options = {
transport: transportType,
logging: signalR.LogLevel.Trace,
accessToken: function () {
return jwtToken;
}
};
hubConnection = new signalR.HubConnection('/authorizedhub', options);
hubConnection.start();
The code in particular in the test file here
I couldn't find a way to solve using angular but I did it using asp.net following this article.
This is what I did: Now to connect I pass the jwt token in the querystring and specify transport type:
const options = {
transport: TransportType.WebSockets
};
this.hubConnection = new HubConnection('http://localhost:27081/hub/notification/?token='+this.jwtToken, options);
And then in the startup.cs > ConfigureServices() :
services
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(cfg =>
{
cfg.RequireHttpsMetadata = false;
cfg.SaveToken = true;
cfg.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["JwtIssuer"],
ValidAudience = Configuration["JwtIssuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["JwtKey"])),
ClockSkew = TimeSpan.Zero // remove delay of token when expire
};
cfg.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
if (context.Request.Query.TryGetValue("token", out StringValues token)
)
{
context.Token = token;
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
var te = context.Exception;
return Task.CompletedTask;
}
};
});