Blazor webassembly - IdentityServer EventSink and HttpContext - authentication

I'm working on a Blazor webassembly application, in ASP.NET Core 3.1 with IdentityServer.
As IdentityServer handle all login, logout, register, ... events, I'm trying to catch theses events in order to obtain some informations about users.
To be clear, I'm trying to memorize the login Date, and to catch new users registration to give them some role automatically.
Here all the services I use in my startup class :
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.Configure<IdentityOptions>(options =>
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier);
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options => {
options.IdentityResources["openid"].UserClaims.Add("name");
options.ApiResources.Single().UserClaims.Add("name");
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
});
// Need to do this as it maps "role" to ClaimTypes.Role and causes issues
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
services.AddAuthentication()
.AddIdentityServerJwt();
I've implemented the IEventSink pattern (http://docs.identityserver.io/en/stable/topics/events.html) :
public class IdentityServerEventSink : IEventSink
{
private readonly UserManager<ApplicationUser> userManager;
private readonly IHttpContextAccessor httpContextAccessor;
public IdentityServerEventSink(UserManager<ApplicationUser> userManager, IHttpContextAccessor httpContextAccessor)
{
this.userManager = userManager;
this.httpContextAccessor = httpContextAccessor;
}
public async Task PersistAsync(Event #event)
{
if (#event.Id.Equals(EventIds.ClientAuthenticationSuccess))
{
var identity = httpContextAccessor.HttpContext.User;
var id = httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier);
var user = await userManager.Users.FirstOrDefaultAsync(u => u.Id == id);
}
}
}
And in the startup.cs :
services.AddHttpContextAccessor();
services.AddTransient<IEventSink, IdentityServerEventSink>();
But when I get in the ClientAuthenticationSuccess event, the identity is always anonymous.
I tried also in the middleware but I have the same issue :
app.Use(async (context, next) =>
{
await next.Invoke();
//handle response
//you may also need to check the request path to check whether it requests image
if (context.User.Identity.IsAuthenticated)
{
var userName = context.User.Identity.Name;
//retrieve uer by userName
using (var dbContext = context.RequestServices.GetRequiredService<ApplicationDbContext>())
{
var user = dbContext.Users.Where(u => u.UserName == userName).FirstOrDefault();
user.LastLogon = System.DateTime.Now;
dbContext.Update(user);
dbContext.SaveChanges();
}
}
});
Do you have any ideas ?
I heard HttpContextAccessor is a bad things in Blazor.

Ok so after an update the middleware delegate works !
app.Use(async (context, next) =>
{
await next.Invoke();
if (context.User.Identity.IsAuthenticated)
{
var userName = context.User.Identity.Name;
using (var dbContext = context.RequestServices.GetRequiredService<ApplicationDbContext>())
{
var user = dbContext.Users.Where(u => u.UserName == userName).FirstOrDefault();
if (user != null)
{
user.LastLogon = System.DateTime.Now;
user.LastIpAddress = context.Connection?.RemoteIpAddress?.ToString();
dbContext.Update(user);
dbContext.SaveChanges();
}
}
}
});
Just be carefull to the order of app.use... in the Configure method ! You need to add this AFTER app.UseIdentityServer(); app.UseAuthentication(); app.UseAuthorization();
But badly the EventSink from IdentityServer still doesn't work, the httpContextAccessor.HttpContext.User.Identity is always anonymous.

Related

How to get identity values from google external provider in ASP.NET Core Web API

[ApiController]
[Route("[controller]")]
public class AuthController : ControllerBase
{
private readonly SignInManager<IdentityUser> _signInManager;
public AuthController(SignInManager<IdentityUser> signInManager)
{
_signInManager = signInManager ?? throw new ArgumentNullException(nameof(signInManager));
}
[HttpGet("token")]
public ChallengeResult Token()
{
var properties = new GoogleChallengeProperties
{
RedirectUri = "/auth/retrieve",
AllowRefresh = true,
};
return Challenge(properties, "Google");
}
[HttpGet("[action]")]
public async Task Retrieve()
{
var token = await HttpContext.GetTokenAsync("access_token");
var externalLoginInfoAsync = await _signInManager.GetExternalLoginInfoAsync();
var identityName = User?.Identity?.Name;
var authenticateResult = await HttpContext.AuthenticateAsync();
}
}
I direct the user to /auth/token, where he is redirected to the Google Oauth Page, if successful, he is redirected to /auth/retrieve, where I expect the user data, but token, externalLoginInfoAsync, identityName, authenticateResult is null
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("Default")));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
services.AddAuthentication()
.AddCookie()
.AddGoogle(options =>
{
options.Scope.Add("https://www.googleapis.com/auth/gmail.settings.basic");
options.AccessType = "offline";
options.SaveTokens = true;
options.SignInScheme = IdentityConstants.ExternalScheme;
options.Events.OnCreatingTicket = ctx =>
{
var identityName = ctx.Identity.Name;
return Task.CompletedTask;
};
options.ClientId = "SMTH_VALUE";
options.ClientSecret = "SMTH_VALUE";
});
services.AddControllers();
}
I debug the google provider and found the user values in the Events - identityName is not null.
How i can get this value in the controller?
You could refer the following code to configure Google authentication in Startup.ConfigureServices method:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddAuthentication()
.AddGoogle(opt =>
{
opt.ClientId = "620831551062-rcvu44q4rhr5d8ossu3m0163jqbjdji0.apps.googleusercontent.com";
opt.ClientSecret = "GXFN0cHBbUlZ6nYLD7a7-cT8";
opt.SignInScheme = IdentityConstants.ExternalScheme;
});
services.AddControllersWithViews();
services.AddRazorPages();
}
Then, use the following sample to login using Google and get user information:
[Authorize]
public class AccountController : Controller
{
private UserManager<ApplicationUser> userManager;
private SignInManager<ApplicationUser> signInManager;
public AccountController(UserManager<ApplicationUser> userMgr, SignInManager<ApplicationUser> signinMgr)
{
userManager = userMgr;
signInManager = signinMgr;
}
// other methods
public IActionResult AccessDenied()
{
return View();
}
[AllowAnonymous]
public IActionResult GoogleLogin()
{
string redirectUrl = Url.Action("GoogleResponse", "Account");
var properties = signInManager.ConfigureExternalAuthenticationProperties("Google", redirectUrl);
return new ChallengeResult("Google", properties);
}
public IActionResult Login()
{
return View();
}
[AllowAnonymous]
public async Task<IActionResult> GoogleResponse()
{
ExternalLoginInfo info = await signInManager.GetExternalLoginInfoAsync();
if (info == null)
return RedirectToAction(nameof(Login));
var result = await signInManager.ExternalLoginSignInAsync(info.LoginProvider, info.ProviderKey, false);
string[] userInfo = { info.Principal.FindFirst(ClaimTypes.Name).Value, info.Principal.FindFirst(ClaimTypes.Email).Value };
if (result.Succeeded)
return View(userInfo);
else
{
ApplicationUser user = new ApplicationUser
{
Email = info.Principal.FindFirst(ClaimTypes.Email).Value,
UserName = info.Principal.FindFirst(ClaimTypes.Email).Value
};
IdentityResult identResult = await userManager.CreateAsync(user);
if (identResult.Succeeded)
{
identResult = await userManager.AddLoginAsync(user, info);
if (identResult.Succeeded)
{
await signInManager.SignInAsync(user, false);
return View(userInfo);
}
}
return AccessDenied();
}
}
}
The result like this:
More detail information, see How to integrate Google login feature in ASP.NET Core Identity and Google external login setup in ASP.NET Core

Authorize with multiple roles

The application is developed on asp.net core 3. Faced with the authorization problem, when the user has many roles.
Roles for users:
public enum UserRole
{
None = 0x0,
View = 0x1,
ConfirmAlarm = 0x2,
ObjectScaling = 0x4,
SchemeEditor = 0x8,
ObjectEditor = 0x10
}
Claims
private async Task Authenticate(User user)
{
var claims = new List<Claim>
{
new Claim(ClaimsIdentity.DefaultNameClaimType, user.Login),
new Claim(ClaimsIdentity.DefaultRoleClaimType, ((UserRole)user.Role).ToString()),
new Claim("uGroupId", user.GroupId.ToString()),
new Claim("uInfo", user.Info),
new Claim("uTheme", user.Theme)
};
ClaimsIdentity id = new ClaimsIdentity(claims, "ApplicationCookie",
ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new
ClaimsPrincipal(id));
}
Controller Method:
[Authorize(Roles = "View")]
[HttpPost("[action]")]
public async Task<string> SomeAction()
{
//...some code
}
If the user has all the roles of the "View, ConfirmAlarm, ObjectScaling, SchemeEditor, ObjectEditor" available, then he cannot be authorized for the method in the controller.
What could be the problem? Need to create a custom AuthorizeAttribute?
UPDATE
Added two classes:
RolesRequirement.cs
public class RolesRequirement : IAuthorizationRequirement
{
public string RoleName { get; }
public RolesRequirement(string roleName)
{
RoleName = roleName;
}
}
RoleHandler.cs
public class RoleHandler : AuthorizationHandler<RolesRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesRequirement requirement)
{
string roles = context.User.Claims.FirstOrDefault(x => x.Type == ClaimsIdentity.DefaultRoleClaimType).Value;
if (!string.IsNullOrEmpty(roles))
{
if (roles.Contains(requirement.RoleName))
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
Add policies to startup.cs
services.AddAuthorization(options =>
{
options.AddPolicy("View", policy => policy.Requirements.Add(new RolesRequirement("View")));
options.AddPolicy("ConfirmAlarm", policy => policy.Requirements.Add(new RolesRequirement("ConfirmAlarm")));
options.AddPolicy("ObjectEditor", policy => policy.Requirements.Add(new RolesRequirement("ObjectEditor")));
});
And add the attributes before the controller method.
[Authorize(Policy = "View")]
[Authorize(Policy = "ObjectEditor")]
[HttpPost("[action]")]
public async Task<string> SomeAction()
{
//...some code
}
Please try below line :
ClaimsIdentity id = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme,
ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
Instead of :
ClaimsIdentity id = new ClaimsIdentity(claims, "ApplicationCookie",
ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType);
CookieAuthenticationDefaults.AuthenticationScheme is Cookies , which matches cookie's default authenticationScheme in Startup.cs:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
Updated :
You can manually check the claims :
services.AddAuthorization(options =>
{
options.AddPolicy("AllowedView", policy =>
{
policy.RequireAssertion(context =>
{
var roles = context.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.Role)?.Value;
var listRolesElements = roles.Split(',').ToList();
return listRolesElements.Contains("View");
});
});
});
And apply policy on the controller which need View permission :
[Authorize(Policy = "AllowedView")]

How to add the UserManager via Dependency Injection in AspNet.Core 2.1?

I've created an Asp.Net Core 2.1 Web Api that uses Bearer Token authentication using Identity Server 4. I have two db contexts, one that is for identity and one that is my data access for my custom application. I want to be able to use the UserManager and UserStore in my Web Api using dependency injection in my controllers.
The problem is that once I add in the "relevant" code for DI, my controller methods are throwing a 302 and trying to redirect me to a login URL. It's like my bearer authentication is being overridden. Below is what my Startup.cs looks like. Any thoughts on how this should be done?
public void ConfigureServices(IServiceCollection services)
{
string connectionString = Configuration.GetConnectionString("DefaultConnection");
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
services.AddDbContext<CustomContext>(options =>
options.UseSqlServer(connectionString));
// start of the stuff that should add the usermanager and userstore
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// end of the stuff
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://authenticate.com";
options.RequireHttpsMetadata = false;
options.ApiName = "api1";
});
services.AddCors(options =>
{
// this defines a CORS policy called "default"
options.AddPolicy("default", policy =>
{
policy.AllowAnyOrigin()
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials();
});
});
}
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.UseCors("default");
app.UseMvc();
}
The controllers look like this:
public class PlayerController : BaseApiController
{
private readonly UserManager<ApplicationUser> userManager;
public PlayerController(UserManager<ApplicationUser> _userManager)
{
userManager = _userManager;
}
.....
}
With BaseApiController like this:
[Route("api/[controller]")]
[Authorize]
public class BaseApiController : Controller
{
...
}
services.AddIdentity<TUser>() adds an "unnecessary" authentication configuration since you already have a bearer scheme from the services.AddAuthentication("Bearer").AddIdentityServerAuthentication() configuration. Take a look at the code below for AddIdentity(). Notice how many schemes were also added (including cookie scheme).
public static IdentityBuilder AddIdentity<TUser, TRole>(
this IServiceCollection services,
Action<IdentityOptions> setupAction)
where TUser : class
where TRole : class
{
// Services used by identity
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddCookie(IdentityConstants.ApplicationScheme, o =>
{
o.LoginPath = new PathString("/Account/Login");
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
};
})
.AddCookie(IdentityConstants.ExternalScheme, o =>
{
o.Cookie.Name = IdentityConstants.ExternalScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
})
.AddCookie(IdentityConstants.TwoFactorRememberMeScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme;
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidateAsync<ITwoFactorSecurityStampValidator>
};
})
.AddCookie(IdentityConstants.TwoFactorUserIdScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
});
// cut for brevity
return new IdentityBuilder(typeof(TUser), typeof(TRole), services);
}
You dont need that. What you do need is:
services.AddIdentityCore<TUser>()
which does not add another authentication configuration.

Asp.Net Core 2 oidc middleware does not challenge after proxy request returns 401

I'm trying to build a centralised proxy that will intercept all requests and handle authentication with openidconnect.
Currently the proxied request simply returns 401, so the middleware suppose to challenge and redirect me to the login page. The issue is using .Net Core 1.1's implemtation it work, but it doesn't seem to work in .Net Core 2.
I've simplified the code but this works, I get redirected to google's signin page.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication();
services.AddProxy();
}
public void Configure(IApplicationBuilder app)
{
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
});
app.UseGoogleAuthentication(new GoogleOptions
{
AutomaticChallenge = true,
SignInScheme = "oidc",
ClientId = "clientId",
ClientSecret = "clientSecret",
});
app.MapWhen(
context => context.RequestStartsWith("http://www.web1.com"),
builder => builder.RunProxy(baseUri: new Uri("http://www.proxy1.com"))
);
}
}
And this doesn't work with .Net Core 2.0's implementation, I'm getting a 401 exception page
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
.AddCookie()
.AddGoogle(options =>
{
options.ClientId = "clientId";
options.ClientSecret = "clientSecret";
});
services.AddProxy();
}
public void Configure(IApplicationBuilder app)
{
app.UseAuthentication();
app.MapWhen(
context => context.RequestStartsWith("http://www.web1.com"),
builder => builder.RunProxy(baseUri: new Uri("http://www.proxy1.com"))
);
}
}
Any ideas?
After looking through the source code, it turns out that Authentication middleware in Asp.Net Core 2 does not challenge after response returns 401 status code, so simply return HttpUnauthorizedResult does not work anymore. The reason Authorize attribute works is it returns a ChallengeResult, which it will eventually will call ChallengeAsync.
The work around is, I've created my own middleware which handles 401 Status Code
public class ChallengeMiddleware
{
private readonly RequestDelegate _next;
private readonly IAuthenticationSchemeProvider _schemes;
public ChallengeMiddleware(RequestDelegate next, IAuthenticationSchemeProvider schemes)
{
_next = next;
_schemes = schemes;
}
public async Task Invoke(HttpContext context)
{
context.Response.OnStarting(async () =>
{
if (context.Response.StatusCode == 401)
{
var defaultChallenge = await _schemes.GetDefaultChallengeSchemeAsync();
if (defaultChallenge != null)
{
await context.ChallengeAsync(defaultChallenge.Name);
}
}
await Task.CompletedTask;
});
await _next(context);
}
}

Authorize doesn't work in Signalr of ASP.NET Core 2.1

I've upgraded my project from ASP.Net Core 2.0 to ASP.NET Core 2.1 by following this tutorial.
Everything was fine until I applied Signar Core 2.1 to my project.
This is my Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IAuthorizationHandler, SolidAccountRequirementHandler>();
services.AddCors(
options => options.AddPolicy("AllowCors",
builder =>
{
builder
.AllowAnyOrigin()
.AllowCredentials()
.AllowAnyHeader()
.AllowAnyMethod();
})
);
services.AddAuthorization(x =>
{
x.AddPolicy("MainPolicy", builder =>
{
builder.Requirements.Add(new SolidAccountRequirement());
});
});
services.AddSignalR();
#region Mvc builder
var authenticationBuilder = services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme);
authenticationBuilder.AddJwtBearer(o =>
{
// You also need to update /wwwroot/app/scripts/app.js
o.SecurityTokenValidators.Clear();
// Initialize token validation parameters.
var tokenValidationParameters = new TokenValidationParameters();
tokenValidationParameters.ValidAudience = "audience";
tokenValidationParameters.ValidIssuer = "issuer";
tokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("SigningKey"));
tokenValidationParameters.ValidateLifetime = false;
o.TokenValidationParameters = tokenValidationParameters;
});
// Construct mvc options.
services.AddMvc(mvcOptions =>
{
////only allow authenticated users
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
.AddRequirements(new SolidAccountRequirement())
.Build();
mvcOptions.Filters.Add(new AuthorizeFilter(policy));
})
.AddJsonOptions(options =>
{
options.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1); ;
#endregion
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
//app.UseHttpsRedirection();
app.UseCors("AllowCors");
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chathub");
});
app.UseMvc();
}
}
This is my SolidRequirementHandler
public class SolidAccountRequirementHandler : AuthorizationHandler<SolidAccountRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, SolidAccountRequirement requirement)
{
context.Succeed(requirement);
return Task.CompletedTask;
}
}
This is my ChatHub.cs:
public class ChatHub : Hub
{
[Authorize(Policy = "MainPolicy")]
public override Task OnConnectedAsync()
{
return base.OnConnectedAsync();
}
}
What I expected was MainPolicy would be called when I used my AngularJS app to connect to ChatHub. However, OnConnectedAsync() function was called without checking request identity.
The policy of MVC Controller was applied successfully, but Signalr's doesn't.
Can anyone help me please ?
Thank you,
I posted this question onto Signalr github issue page.
Here is the answer they gave me .
I tried and it worked successfully:
The solution is to put [Authorize] attribute onto ChatHub
[Authorize(Policy = "MainPolicy")]
public class ChatHub : Hub
{
public override Task OnConnectedAsync()
{
return base.OnConnectedAsync();
}
}
Just share to who doesn't know :)
I have the same problem, there are four key things:
1- In your Startup.cs be aware of this Order inside Configure(IApplicationBuilder app)
app.UseRouting();
app.UseAuthorization( );
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<myChat>("/chat");
});
the app.UseAuthorization( ); should always be between app.UseRouting(); and app.UseEndpoints().
2- SignalR doesn't send Tokens in Header but it sends them in Query. In your startup.cs inside ConfigureServices(IServiceCollection services) You have to configure your app in a way to read Tokens from the query and put them in the header. You can Configure your JWT in this way:
services.AddAuthentication()
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidIssuer = [Issuer Site],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes([YOUR SECRET KEY STRING]))
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var path = context.Request.Path;
var accessToken = context.Request.Query["access_token"];
if (!string.IsNullOrEmpty(accessToken) && path.StartsWithSegments("/chat"))
{
context.Request.Headers.Add("Authorization", new[] { $"Bearer {accessToken}" });
}
return Task.CompletedTask;
}
};
});
3- Your Client should send Token when it wants to establish a connection. You can add token to Query when building the connection.
var connection = new signalR.HubConnectionBuilder().withUrl(
"http://localhost:5000/chat", {
skipNegotiation: true,
transport: signalR.HttpTransportType.WebSockets,
accessTokenFactory: () => "My Token Is Here"}).build();
4- I didn't add a default Athuentication scheme inside services.AddAuthentication()
So every time I have to specify my authorization scheme like this. [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
And Finally, You can Protect your Chat Class Like this
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authentication.JwtBearer;
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class myChat : Hub
{
///Some functions
}
It seems that Using statements is important, So make sure using the right ones.
SignalR hub Authorize attribute doesn't work
Note: I have a problem with Authorizing only a single method in the myChat class. I don't know why.