.Net Core 2.0 Web API using Identity / JWT and having user manager work with DI - jwt

I have the same problem as this:
.Net Core 2.0 Web API using JWT - Adding Identity breaks the JWT authentication
If you don't add:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<IdentityDb>()
.AddDefaultTokenProviders();
You can't Dependency inject the user manager, and sign in manager, to the Token Controller or other controllers.
Any ideas how you fix that?

I added AddDefaultTokenProviders() to services.AddIdentityCore
so my code now looks like this:
services.AddDbContext<IdentityDb>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
IdentityBuilder builder = services.AddIdentityCore<ApplicationUser>
(opt =>
{
opt.Password.RequireDigit = true;
opt.Password.RequiredLength = 8;
opt.Password.RequireNonAlphanumeric = false;
opt.Password.RequireUppercase = true;
opt.Password.RequireLowercase = true;
}
).AddDefaultTokenProviders();
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder
.AddEntityFrameworkStores<IdentityDb>();
//.AddDefaultTokenProviders();
builder.AddRoleValidator<RoleValidator<IdentityRole>>();
builder.AddRoleManager<RoleManager<IdentityRole>>();
builder.AddSignInManager<SignInManager<ApplicationUser>>();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "WindowLink.Security.Bearer",
ValidAudience = "WindowLink.Security.Bearer",
IssuerSigningKey = JwtSecurityKey.Create("windowlink-secret-key")
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
Console.WriteLine("OnAuthenticationFailed: " + context.Exception.Message);
return Task.CompletedTask;
},
OnTokenValidated = context =>
{
Console.WriteLine("OnTokenValidated: " + context.SecurityToken);
return Task.CompletedTask;
}
};
});
services.AddAuthorization(options =>
{
options.AddPolicy("Member",
policy => policy.RequireClaim("MembershipId"));
});
services.AddMvc();
That did the trick for me.
Question can now be closed.

Related

JWT Token Authentication - fetch public key from API and use it in TokenValidationParameters in Program.cs of client app

I'm trying to implement a Single Sign On API that uses JWT Token Authentication with asymmetric encryption. I've tested the authentication with success on some endpoint of my API.
Now I want to use this authentication into another web app project (client), the problem is I don't know how to import or fetch the public key from my SSO API in configuration in Program.cs.
SSO API Program.cs
builder.Services.AddSingleton<RsaSecurityKey>(provider => {
RSA rsa = RSA.Create();
rsa.ImportRSAPublicKey(
source: Convert.FromBase64String(builder.Configuration.GetValue<string>("JwtConfig:Asymmetric:PublicKey")),
bytesRead: out int _
);
return new RsaSecurityKey(rsa);
});
builder.Services.AddAuthentication().AddJwtBearer("Asymmetric", options =>
{
SecurityKey rsa = builder.Services.BuildServiceProvider().GetRequiredService<RsaSecurityKey>();
options.IncludeErrorDetails = true; // great for debugging
// Configure the actual Bearer validation
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = rsa,
ValidAudience = "jwt-test",
ValidIssuer = "jwt-test",
RequireSignedTokens = true,
RequireExpirationTime = true, // <- JWTs are required to have "exp" property set
ValidateLifetime = true, // <- the "exp" will be validated
ValidateAudience = true,
ValidateIssuer = true,
};
options.MapInboundClaims = false;
});
Endpoit to return public key
[HttpPost]
[Route("JWK")]
public async Task<string> GetPublicKey()
{
return _configuration["JwtConfig:Asymmetric:PublicKey"];
}
Client Webapp Program.cs
builder.Services.AddAuthentication().AddJwtBearer("Asymmetric", options =>
{
options.IncludeErrorDetails = true; // great for debugging
// Configure the actual Bearer validation
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = --> some way of fetching the publick key from API (https:.../api/JWK),
ValidAudience = "jwt-test",
ValidIssuer = "jwt-test",
RequireSignedTokens = true,
RequireExpirationTime = true, // <- JWTs are required to have "exp" property set
ValidateLifetime = true, // <- the "exp" will be validated
ValidateAudience = true,
ValidateIssuer = true,
};
// pt a nu-mi mai schimba claims cu acele link-uri xml
options.MapInboundClaims = false;
});
How can I fetch the public key in this stage of building?
Is this the correct way of configuration for using jwt authentication provided by third party app?
I don't think use httpclient to get key from other projects is a good method, Because you can't mare sure if it is safety. If you wanna share key between projects, A common method is to put this key in to a public, safe place. Here i recommend you to use Azure key vault or Azure Blobs, you can refer to this link. If your Api project and another web app project use the same database, You can also save the key into database.
So I managed to make it work like following, I don't know how bad it is but...
builder.Services.AddSingleton<RsaSecurityKey>(provider =>
{
HttpClient httpClient = new HttpClient();
string pKey = "";
//https://localhost:7206/api/Authentication/JWK -> returns a string with my public key
HttpResponseMessage response = Task.Run(() => httpClient.GetAsync("https://localhost:7206/api/Authentication/JWK")).GetAwaiter().GetResult();
if (response.IsSuccessStatusCode)
{
pKey = Task.Run(() => response.Content.ReadAsStringAsync()).Result;
}
RSA rsa = RSA.Create();
rsa.ImportRSAPublicKey(
//source: Convert.FromBase64String(builder.Configuration.GetValue<string>("JwtConfig:Asymmetric:PublicKey")),
source: Convert.FromBase64String(pKey.ToString()),
bytesRead: out int _
);
return new RsaSecurityKey(rsa);
});
builder.Services.AddAuthentication().AddJwtBearer("Asymmetric", options =>
{
SecurityKey rsa = builder.Services.BuildServiceProvider().GetRequiredService<RsaSecurityKey>();
options.IncludeErrorDetails = true; // great for debugging
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKey = rsa,
ValidAudience = "jwt-test",
ValidIssuer = "jwt-test",
RequireSignedTokens = true,
RequireExpirationTime = true, // <- JWTs are required to have "exp" property set
ValidateLifetime = true, // <- the "exp" will be validated
ValidateAudience = true,
ValidateIssuer = true,
};
options.MapInboundClaims = false;
});
Client for test
[Authorize(AuthenticationSchemes = "Asymmetric")] // Use the "Asymmetric" authentication scheme
public async Task<IActionResult> Index()
{
return Ok("Succes!");
}

OpenIdConnect Authentication Cookie not being deleted on signing out

I'm trying to implement OpenIdConnect as my authentication provider, using .NET6. I have the sign-in part working properly, however, on sign-out, the authentication cookie is not being deleted.
I've seen plenty of other threads referencing similar problems (this, for example provides a pretty good summary of several of these), but none of the approaches seem to work.
Broadly speaking, my authentication code is configured as follows:
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = AuthenticateScheme;
options.DefaultSignOutScheme = AuthenticateScheme;
options.DefaultChallengeScheme = ChallengeScheme;
})
.AddCookie(AuthenticateScheme, options =>
{
//trying to avoid having to add this!
//options.Events.OnSigningOut = async ctx =>
//{
// await Task.Run(() => ctx.HttpContext.Response.Cookies.Delete(CookieName));
//};
})
.AddOpenIdConnect(ChallengeScheme, options =>
{
options.SignInScheme = AuthenticateScheme;
options.SignOutScheme = AuthenticateScheme;
options.ResponseType = OpenIdConnectResponseType.Code;
options.CallbackPath = CallbackPath;
options.UsePkce = true;
options.Authority = Authority;
options.ClientId = ClientId;
options.ClientSecret = ClientSecret;
options.Scope.Clear();
options.Scope.Add(OpenIdConnectScope.OpenId);
options.Scope.Add(OpenIdConnectScope.OfflineAccess);
options.Scope.Add(OpenIdConnectScope.Email);
options.Scope.Add(OpenIdConnectScope.OpenIdProfile);
options.MapInboundClaims = false;
options.TokenValidationParameters = new TokenValidationParameters
{
RoleClaimType = "roles",
NameClaimType = "preferred_username",
ValidateIssuer = false
};
options.Events.OnRedirectToIdentityProvider = ctx =>
{
// Prevent redirect loop
if (ctx.Response.StatusCode == 401)
{
ctx.HandleResponse();
}
return Task.CompletedTask;
};
options.Events.OnAuthenticationFailed = context =>
{
context.HandleResponse();
context.Response.BodyWriter.WriteAsync(Encoding.ASCII.GetBytes(context.Exception.Message));
return Task.CompletedTask;
};
});
And over on my Startup.cs, I have the following code within the Configure method:
app.Map("/Logout", map =>
{
map.Run(async appBuilder =>
{
// tried this, didn't work
//await appBuilder.SignOutAsync("MyAuthCookie", new AuthenticationProperties { RedirectUri = "/" });
// tried this, didn't work
//await appBuilder.SignOutAsync();
});
});
app.UseNotFoundHandler();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseGetaCategories();
app.UseGetaCategoriesFind();
app.UseAnonymousId();
app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(name: "Default", pattern: "{controller}/{action}/{id?}");
endpoints.MapControllers();
endpoints.MapRazorPages();
endpoints.MapContent();
});
Any suggestions would be greatly appreciated. For now, the only way I've been able to get this to work is to explicitly delete the cookie within the OnSigningOut event, but this doesn't feel like the right approach.
A more complete logout should look something like this:
/// <summary>
/// Do the logout
/// </summary>
/// <returns></returns>
[HttpPost]
[ValidateAntiForgeryToken]
public async Task Logout()
{
await HttpContext.SignOutAsync(AuthenticateScheme);
await HttpContext.SignOutAsync(ChallengeScheme);
//Important, this method should never return anything.
}
You might need to tweak the schema names if you don't use the default ones.
In the AddAuthentication method, I would se these settings, you want cookie for everything except the challenge.
services.AddAuthentication(options =>
{
options.DefaultScheme = AuthenticateScheme;
options.DefaultChallengeScheme = ChallengeScheme;
})

asp.net core webapi JWT validation using authorization filter and JWKS

I am writing an webapi using .net core 6. It need to validate user Authorization JWT token using Authorisation filter and JWKS (keys). May I have any example. I have search google and lots of example are using middleware.
I would like to use Authorisation filter + JWKS
Token header contain below
{
"alg": "RS256",
"typ": "JWT",
**"kid": "asdfb6T82zfdfdfJwc2eruee"**
}
Please any sample code, example would be excellent.
I'm not familiar with jwks and I tried with jwt token and validate the token in filter as below:
public class JwTFilter : IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var jwt = context.HttpContext.Request.Headers["Authorization"].ToString();
var validateParameters = new TokenValidationParameters()
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("1234567890123456")),
ValidateIssuer = true,
ValidIssuer = "http://localhost:5000",
ValidateAudience = true,
ValidAudience = "http://localhost:5001",
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5)
};
var valiresult = ValidateToken(jwt, validateParameters);
if (valiresult == false)
{
context.HttpContext.Response.StatusCode = 401;
context.Result = new JsonResult(new errmessage() { statuecode = "401", err = "Auth Failed" });
}
}
private static bool ValidateToken(string token, TokenValidationParameters validationParameters)
{
var tokenHandler = new JwtSecurityTokenHandler();
try
{
tokenHandler.ValidateToken(token, validationParameters, out var validatedToken);
return true;
}
catch (Exception)
{
return false;
}
}
}
public class errmessage
{
public string statuecode { get; set; }
public string err { get; set; }
}
In startup class:
services.AddControllers(options =>
{
options.Filters.Add<JwTFilter>();
});
The ResulT:
Update:
Is it what you want?
public override void OnAuthorization(HttpActionContext context)
{
try
{
var access_token = context.Request.Headers.Authorization?.Parameter;
if (String.IsNullOrEmpty(access_token))
{
//401
context.HttpContext.Response.StatusCode = 401;
context.Result = new JsonResult(new errmessage() { statuecode = "401", err = "Auth Failed" });
}
else
{
//access the public key
var httpclient = new HttpClient();
var jwtKey = httpclient.GetStringAsync(somejwksendpoint).Result;
//chache jwks
var Ids4keys = JsonConvert.DeserializeObject<Ids4Keys>(jwtKey);
var jwk = Ids4keys.keys;
var parameters = new TokenValidationParameters
{
ValidIssuer = issUrl,
IssuerSigningKeys = jwk,
ValidateLifetime = true,
ValidAudience = apiName
};
var handler = new JwtSecurityTokenHandler();
//validate
var vali = handler.ValidateToken(access_token, parameters, out var _);
}
}
catch (Exception ex)
{
context.HttpContext.Response.StatusCode = 401;
context.Result = new JsonResult(new errmessage() { statuecode = "401", err = "Auth Failed" });
}
}
.....
public class Ids4Keys
{
public JsonWebKey[] keys { get; set; }
}

Invalid JWT bearer token on .net 6.0 web api

I am trying to implement security to my app and used Identity and JWT bearer token to authenticate but I always get the invalid token response in my swagger. Tried many solutions but I still get the same response.
[HttpPost("[action]")]
public async Task<IActionResult> Login(LoginBindingModel login)
{
IActionResult actionResult;
var user = await userManager.FindByEmailAsync(login.Email);
if (user == null)
{
actionResult = NotFound(new {errors = new[] {$"User with email {login.Email} is not found."}});
}
else if (await userManager.CheckPasswordAsync(user, login.Password))
{
if(!user.EmailConfirmed)
{
actionResult = BadRequest(new { errors = new[] { $"Email not confirmed." } });
}
else
{
var token = GenerateTokenAsync(user);
actionResult = Ok(token);
}
}
else
{
actionResult = BadRequest(new { errors = new[] { $"Password is not valid." } });
}
return actionResult;
}
private string GenerateTokenAsync(IdentityUser user)
{
IList<Claim> userClaims = new List<Claim>
{
new Claim("UserName", user.UserName),
new Claim("Email", user.Email)
};
var x = jwtOptions.SecurityKey;
return new JwtSecurityTokenHandler().WriteToken(new JwtSecurityToken(
claims: userClaims,
expires: DateTime.UtcNow.AddMonths(1),
signingCredentials: new SigningCredentials(jwtOptions.SecurityKey, SecurityAlgorithms.HmacSha256)));
}
Program.cs
using ASPNetCoreMasterAPIAssignment2.Filters;
using DomainModels;
using Infrastructure.Data.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Repositories;
using Services;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
builder.Services.AddSwaggerGen(x =>
{
x.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please insert JWT token with bearer into field",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey
});
x.AddSecurityRequirement(new OpenApiSecurityRequirement {
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
});
builder.Services.AddScoped<IItemService, ItemService>();
builder.Services.AddScoped<IItemRepository, ItemRepository>();
builder.Services.AddDbContext<ItemDbContext>(opt =>
{
opt.UseSqlServer(builder.Configuration.GetConnectionString("default"));
});
builder.Services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ItemDbContext>()
.AddDefaultTokenProviders();
SecurityKey key = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(builder.Configuration["jwt:secret"]));
builder.Services.Configure<JWTOptions>(_ => _.SecurityKey = key);
builder.Services.AddAuthentication(opt =>
{
opt.DefaultAuthenticateScheme = "Bearer";
opt.DefaultChallengeScheme = "Bearer";
opt.DefaultScheme = "Bearer";
})
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
IssuerSigningKey = key
};
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI();
}
else
{
app.UseExceptionHandler("/error");
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"default": "Server=localhost;Database=EfCoreDb;Trusted_Connection=True;"
},
"jwt": {
"secret": "5a927360-790b-4ba5-bae1-09aa98364090"
}
}
when i add the [Authorized] attribute to a controller, i get the following error
invalid_token can be caused by a lot of cases.
Try to view the root cause by handling the OnChallenge method (in particular the content of context.AuthenticateFailure):
return builder.AddJwtBearer(options =>
{
options.Authority = issuer;
options.Audience = audience;
options.TokenValidationParameters = new TokenValidationParameters()
{
ClockSkew = new System.TimeSpan(0, 0, 30)
};
options.Events = new JwtBearerEvents()
{
OnChallenge = context =>
{
context.HandleResponse();
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
context.Response.ContentType = "application/json";
// Ensure we always have an error and error description.
if (string.IsNullOrEmpty(context.Error))
context.Error = "invalid_token";
if (string.IsNullOrEmpty(context.ErrorDescription))
context.ErrorDescription = "This request requires a valid JWT access token to be provided";
// Add some extra context for expired tokens.
if (context.AuthenticateFailure != null && context.AuthenticateFailure.GetType() == typeof(SecurityTokenExpiredException))
{
var authenticationException = context.AuthenticateFailure as SecurityTokenExpiredException;
context.Response.Headers.Add("x-token-expired", authenticationException.Expires.ToString("o"));
context.ErrorDescription = $"The token expired on {authenticationException.Expires.ToString("o")}";
}
return context.Response.WriteAsync(JsonSerializer.Serialize(new
{
error = context.Error,
error_description = context.ErrorDescription
}));
}
};
});
source: https://sandrino.dev/blog/aspnet-core-5-jwt-authorization#configuring-jwt-bearer-authentication
In my case, it was resolved by updating some nuget, specially Microsoft.IdentityModel.Tokens : https://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet/issues/1792

signalr core (2.1) JWT authentication hub/negotiate 401 Unauthorized

so I have a .net core (2.1) API that uses JWT tokens for authentication. I can login and make authenticated calls successfully.
I am using React (16.6.3) for the client, which getting a JWT code and making authenticated calls to the API works.
I am trying to add signalr hubs to the site. If I do not put an [Authorize] attribute on the hub class. I can connect, send and receive messages (its a basic chathub at the moment).
when I add the [Authorize] attribute to the class, the React app will make an HttpPost to example.com/hubs/chat/negotiate . I would get a 401 status code. the Authorization: Bearer abc..... header would be passed up.
To build the hub in React I use:
const hubConn = new signalR.HubConnectionBuilder()
.withUrl(`${baseUrl}/hubs/chat`, { accessTokenFactory: () => jwt })
.configureLogging(signalR.LogLevel.Information)
.build();
where the jwt variable is the token.
I have some setup for the authentication:
services.AddAuthentication(a =>
{
a.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
a.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = false;
options.Audience = jwtAudience;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = jwtIssuer,
ValidAudience = jwtAudience,
RequireExpirationTime = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtKey)),
};
// 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"];
var authToken = context.Request.Headers["Authorization"].ToString();
var token = !string.IsNullOrEmpty(accessToken) ? accessToken.ToString() : !string.IsNullOrEmpty(authToken) ? authToken.Substring(7) : String.Empty;
var path = context.HttpContext.Request.Path;
// If the request is for our hub...
if (!string.IsNullOrEmpty(token) && path.StartsWithSegments("/hubs"))
{
// Read the token out of the query string
context.Token = token;
}
return Task.CompletedTask;
}
};
});
the OnMessageReceived event does get hit and context.Token does get set to the JWT Token.
I can't figure out what I am doing wrong to be able to make authenticated calls for signalr core.
solution
I updated my code to use 2.2 (not sure if this was actually required).
so I spent some time looking at the source code, and the examples within:
https://github.com/aspnet/AspNetCore
I had a Signalr CORS issue which was solved with:
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.SetIsOriginAllowed((host) => true) //allow all connections (including Signalr)
);
});
the important part being .SetIsOriginAllowed((host) => true) This allows all connections for both website and signalr cors access.
I had not added
services.AddAuthorization(options =>
{
options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
{
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireClaim(ClaimTypes.NameIdentifier);
});
});
I had only used services.AddAuthentication(a =>
I took the following directly from the samples in github
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(accessToken) &&
(context.HttpContext.WebSockets.IsWebSocketRequest || context.Request.Headers["Accept"] == "text/event-stream"))
{
context.Token = context.Request.Query["access_token"];
}
return Task.CompletedTask;
}
};
Not sure if this was needed in the attribute, but the same used it on its hubs
[Authorize(JwtBearerDefaults.AuthenticationScheme)]
with that I could not get multiple website and console apps to connect and communicate via signalr.
To be used with [Authorize] you need to set the request header. Since web sockets do not support headers, the token is passed with the query string, which you correctly parse. The one thing that's missing is
context.Request.Headers.Add("Authorization", "Bearer " + token);
Or in your case probably context.HttpContext.Request.Headers.Add("Authorization", "Bearer " + token);
Example:
This is how i do it. On the client:
const signalR = new HubConnectionBuilder().withUrl(`${this.hubUrl}?token=${token}`).build();
On the server, in Startup.Configure:
app.Use(async (context, next) => await AuthQueryStringToHeader(context, next));
// ...
app.UseSignalR(r => r.MapHub<SignalRHub>("/hubUrl"));
Implementation of AuthQueryStringToHeader:
private async Task AuthQueryStringToHeader(HttpContext context, Func<Task> next)
{
var qs = context.Request.QueryString;
if (string.IsNullOrWhiteSpace(context.Request.Headers["Authorization"]) && qs.HasValue)
{
var token = (from pair in qs.Value.TrimStart('?').Split('&')
where pair.StartsWith("token=")
select pair.Substring(6)).FirstOrDefault();
if (!string.IsNullOrWhiteSpace(token))
{
context.Request.Headers.Add("Authorization", "Bearer " + token);
}
}
await next?.Invoke();
}
best method for using authorization for hubs is to force the application add the jwt token from the query string to the context
and its working for me via this method
put this inside your program.cs (dot net 6):
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = builder.Configuration["Jwt:Issuer"],
ValidAudience = builder.Configuration["Jwt:Audience"],
IssuerSigningKey = new SymmetricSecurityKey
(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])),
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = false,
ValidateIssuerSigningKey = true
};
o.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var accessToken = context.Request.Query["access_token"];
if (string.IsNullOrEmpty(accessToken) == false)
{
context.Token = accessToken;
}
return Task.CompletedTask;
}
};
});
and my hubs are like this its using the main authorize method of asp
[Microsoft.AspNetCore.Authorization.Authorize]
public async Task myhub()
{
//do anything in your hub
}
youtube totorial that helped me to solve this problem