Dashboard Authorization (self-hosted as a Windows service) - c#

I have a Hangfire instance hosted in a windows service using Topshelf:
static void Main()
{
HostFactory.Run(x =>
{
x.Service<Application>(s =>
{
s.ConstructUsing(name => new Application());
s.WhenStarted(tc => tc.Start());
s.WhenStopped(tc => tc.Stop());
});
x.RunAsLocalSystem();
x.SetDescription("Hangfire Windows Service Sample");
x.SetDisplayName("Hangfire Windows Service Sample");
x.SetServiceName("hangfire-sample");
});
}
private class Application
{
private IDisposable host;
public void Start()
{
host = WebApp.Start<Startup>("http://localhost:12345");
Console.WriteLine();
Console.WriteLine("Hangfire Server started.");
Console.WriteLine("Dashboard is available at {0}/hangfire", configSettings.Jobs.EndPoint);
Console.WriteLine();
}
public void Stop()
{
host.Dispose();
}
}
My StartUp class is pretty basic:
public void Configuration(IAppBuilder app)
{
GlobalConfiguration.Configuration.UseSqlServerStorage(
"DefaultConnection",
new SqlServerStorageOptions {QueuePollInterval = TimeSpan.FromMinutes(1)});
app.UseHangfireDashboard("/hangfire", new DashboardOptions
{
AuthorizationFilters = new[] { new AuthorizationFilter() }
});
app.UseHangfireServer();
}
I'm trying to use a custom authorization filter:
public class AuthorizationFilter : IAuthorizationFilter
{
public bool Authorize(IDictionary<string, object> owinEnvironment)
{
var context = new OwinContext(owinEnvironment);
return context.Authentication.User.Identity.IsAuthenticated;
}
}
I was hoping to use context.Authentication.User to authenticate but it always returns null.
Is there any way to make this work for a self-hosted hangfire service?

Related

Can I avoid using BuildServiceProvider in startup?

Can I avoid using BuildServiceProvider in ExpireToken function below? In other words...
Is there a way of avoiding BuildServiceProvider() in Startup.cs when using JWTBearer OnAuthenticationFailed event?
(Using Web API aspnet Core 3.0)
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//some code removed..
//Authentication
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents()
{
OnAuthenticationFailed = context =>
{
if (context.Exception.Message.Contains("The token is expired"))
{
Microsoft.Extensions.Primitives.StringValues token = string.Empty;
var logger = new LoggerManager();
logger.LogError("Token expired"); //confirming that the token expired so authentication failed
var header = context.Request.Headers;
if (!header.TryGetValue("Authorization", out token))
logger.LogError("no token found");
else
token = token.ToString().Substring("Bearer ".Length).Trim();
ExpireToken(services, token);
}
return Task.CompletedTask;
}
};
});
}
public void ExpireToken(IServiceCollection services, string tokenId)
{
var sp = services.BuildServiceProvider();
var jwtManager = sp.GetService<IJWTAuthenticationManager>();
jwtManager.ExpireToken(tokenId);
}
jwtAuthentication Manager will publish this event so that subscribers are notified (c#:event + delegates)
public delegate void TokenExpiredEventHandler(object source, TokenExpiredEventArgs args);
JWTAuthenticationManager.cs
public class JWTAuthenticationManager : IJWTAuthenticationManager
{
private readonly string _tokenKey;
public event TokenExpiredEventHandler TokenExpired;
//some code removed for brevity
public void ExpireToken(string tokenId)
{
OnTokenExpired(tokenId); //notify all subscribers
}
protected virtual void OnTokenExpired(string token) {
TokenExpired?.Invoke(this, new TokenExpiredEventArgs(token));
}
}
public class TokenExpiredEventArgs : EventArgs
{
public string token;
public TokenExpiredEventArgs(string tokenId)
{
token = tokenId;
}
}
Program.cs
public static void Main(string[] args)
{
var host = CreateWebHostBuilder(args).Build();
var jwtMgr = host.Services.GetRequiredService<IJWTAuthenticationManager>();
var cacheMgr = host.Services.GetRequiredService<ICacheManager>();
jwtMgr.TokenExpired += cacheMgr.OnTokenExpired;
host.Run();
}
Why not resolve services from context? Simply change your ExpireToken method to take an IServiceProvider:
public void ExpireToken(IServiceProvider services, string tokenId)
{
var jwtManager = services.GetService<IJWTAuthenticationManager>();
jwtManager.ExpireToken(tokenId);
}
And then pass context.HttpContext.RequestServices:
ExpireToken(context.HttpContext.RequestServices, token);

Asp.Net Core running SignalR in multiple browser tabs

I'm creating an ASP.NET Core Application running on 4.7 with an Angular front-end. As I need to notify the users about status changes I'm trying to add SignalR. I'm using following versions:
AspNetCore 2.0.1
Angular 5.x
SignalR 1.0.0-alpha2-final
Below you can see my code. If I run this app I can see in the logs that both hub connections get started. If I open a second browser tab with the same URL, only one connection is successful. For the second hub I always get this error:
Error: Not Found
at XMLHttpRequest.xhr.onload [as __zone_symbol__ON_PROPERTYload] (http://localhost:51616/dist/estructuring_main.js?v=fBx34vrzWnqBIPjAm71NaWQz0TEeQZWcx2ixKnjADWI:36168:28)
at XMLHttpRequest.wrapFn (http://localhost:51616/dist/vendor.js?v=rp1X6LsN5SJnsTJrd-749xunuUaKAA_WINwoqzbaeqE:120085:39)
at ZoneDelegate.invokeTask (http://localhost:51616/dist/vendor.js?v=rp1X6LsN5SJnsTJrd-749xunuUaKAA_WINwoqzbaeqE:119340:31)
at Object.onInvokeTask (http://localhost:51616/dist/vendor.js?v=rp1X6LsN5SJnsTJrd-749xunuUaKAA_WINwoqzbaeqE:5013:33)
at ZoneDelegate.invokeTask (http://localhost:51616/dist/vendor.js?v=rp1X6LsN5SJnsTJrd-749xunuUaKAA_WINwoqzbaeqE:119339:36)
at Zone.runTask (http://localhost:51616/dist/vendor.js?v=rp1X6LsN5SJnsTJrd-749xunuUaKAA_WINwoqzbaeqE:119107:47)
at ZoneTask.invokeTask [as invoke] (http://localhost:51616/dist/vendor.js?v=rp1X6LsN5SJnsTJrd-749xunuUaKAA_WINwoqzbaeqE:119415:34)
at invokeTask (http://localhost:51616/dist/vendor.js?v=rp1X6LsN5SJnsTJrd-749xunuUaKAA_WINwoqzbaeqE:120436:14)
at XMLHttpRequest.globalZoneAwareCallback (http://localhost:51616/dist/vendor.js?v=rp1X6LsN5SJnsTJrd-749xunuUaKAA_WINwoqzbaeqE:120462:17)
The error says 404 not found even though I can see the request in my request-middleware.
What do I need to change that the hub connections also work with multiple tabs?
Startup.cs
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddSingleton(typeof(IUserTracker<>), typeof(InMemoryUserTracker<>));
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime appLifetime)
{
app.UseSignalR(routes =>
{
routes.MapHub<StatusHub>("status", (options) =>
{
options.Transports = TransportType.LongPolling;
});
routes.MapHub<AutopricerHub>("autopricer", (options) =>
{
options.Transports = TransportType.LongPolling;
});
});
}
InMemoryUserTracker.cs
public class InMemoryUserTracker<THub> : IUserTracker<THub>
{
private readonly ConcurrentDictionary<HubConnectionContext, UserDetails> usersOnline
= new ConcurrentDictionary<HubConnectionContext, UserDetails>();
public IEnumerable<UserDetails> UsersOnline()
=> usersOnline.Values.AsEnumerable();
public Task AddUser(HubConnectionContext connection, UserDetails userDetails)
{
usersOnline.TryAdd(connection, userDetails);
return Task.CompletedTask;
}
public Task RemoveUser(HubConnectionContext connection)
{
usersOnline.TryRemove(connection, out var userDetails);
return Task.CompletedTask;
}
}
BaseHubWithPresence.cs
public abstract class BaseHubWithPresence : Hub
{
protected readonly IUserTracker<BaseHubWithPresence> userTracker;
protected BaseHubWithPresence(IUserTracker<BaseHubWithPresence> userTracker)
{
this.userTracker = userTracker;
}
public virtual async Task Join(long userId)
{
await userTracker.AddUser(Context.Connection, new UserDetails(Context.ConnectionId, userId));
await Task.CompletedTask;
}
public virtual async Task Leave()
{
await userTracker.RemoveUser(Context.Connection);
await Task.CompletedTask;
}
}
AutopricerHub.cs
public class AutopricerHub : BaseHubWithPresence
{
public AutopricerHub(IUserTracker<AutopricerHub> userTracker)
: base(userTracker) {}
public async Task SendToAll()
{
await Clients.All.InvokeAsync("All", "data");
}
public async Task SendUpdateAsync(long userId)
{
foreach (var user in userTracker.UsersOnline().Where(o => o.UserId == userId))
{
await Clients.Client(user.ConnectionId).InvokeAsync("Update", "data");
}
}
}
StatusHub.cs
public class StatusHub : BaseHubWithPresence
{
public StatusHub(IUserTracker<BaseHubWithPresence> userTracker)
: base(userTracker)
{
}
public async Task SendSingleAsync(long listener)
{
foreach (var user in userTracker.UsersOnline().Where(o => o.UserId == listener))
{
await Clients.Client(user.ConnectionId).InvokeAsync("Update", "data");
}
}
public async Task SendMultipleAsync(IReadOnlyCollection<long> listeners)
{
foreach (var listener in listeners)
{
foreach (var user in userTracker.UsersOnline().Where(o => o.UserId == listener))
{
await Clients.Client(user.ConnectionId).InvokeAsync("Update", "data");
}
}
}
}
price-notification.service.ts
#Injectable()
export class PriceNotificationService {
private hubConnection: HubConnection;
private destroy$ = new Subject<boolean>();
constructor(
private appContextService: AppContextService,
private messageService: MessageService) {
this.init();
}
private init(): void {
this.hubConnection = new HubConnection('/autopricer', { transport: TransportType.LongPolling });
this.hubConnection.start()
.then(() => {
this.appContextService.getAppContext().takeUntil(this.destroy$).subscribe(appContext => {
this.hubConnection.invoke('join', appContext.FirmuserId);
});
DEBUG.init && debug('Price Notification Hub connection started');
})
.catch(err => {
DEBUG.init && debug('Error while establishing Price Notification Hub connection');
});
this.hubConnection.onclose = e => {
DEBUG.functionCalls && debug('On close Price Notification Hub connection');
this.hubConnection.invoke('leave');
};
this.hubConnection.off('leave', null);
}
private showUpdateNotification(data: string) {
}
private showAllNotification(data: string) {
}
}
status-notification.service.ts
#Injectable()
export class StatusNotificationService {
private hubConnection: HubConnection;
private destroy$ = new Subject<boolean>();
constructor(
private appContextService: AppContextService,
private messageService: MessageService) {
this.init();
}
private init(): void {
this.hubConnection = new HubConnection('/status', { transport: TransportType.LongPolling });
this.hubConnection.start()
.then(() => {
this.appContextService.getAppContext().takeUntil(this.destroy$).subscribe(appContext => {
this.hubConnection.invoke('join', appContext.FirmuserId);
});
DEBUG.init && debug('Status Notification Hub connection started');
})
.catch(err => {
DEBUG.init && debug('Error while establishing Status Notification Hub connection');
});
this.hubConnection.onclose = e => {
DEBUG.functionCalls && debug('On close Status Notification Hub connection');
this.hubConnection.invoke('leave');
};
this.hubConnection.off('leave', null);
}
private showUpdateNotification(data: string) {
}
}
The service provider for each of these Angular service is in a separate component.
Browser network tab:

Hangfire Dashboard authorization ends up in infinite loop

I've configured hangfire server on my local machine. I use dashboard and added dashboard authorization as it was described in this documentation https://github.com/HangfireIO/Hangfire.Dashboard.Authorization
public void Configuration(IAppBuilder app)
{
GlobalConfiguration.Configuration.UseSqlServerStorage("HangfireDB");
app.UseHangfireServer();
var options = new DashboardOptions()
{
Authorization = new IDashboardAuthorizationFilter[]
{
new DashboardAuthorization()
}
};
app.UseHangfireDashboard("/hangfire", options);
InitializeJobs();
}
public class DashboardAuthorization : IDashboardAuthorizationFilter
{
public bool Authorize([NotNull] DashboardContext context)
{
string ipAddress = HttpContext.Current.Request.UserHostAddress;
return CommonUtils.IsIpAddressValid(ipAddress.Trim());
}
}
The problem is that the Authorize function is called infinitely. Any solution?

How to inject SignalR hub to a regular class using SimpleInjector

In a console app I'm using
OWIN
Signalr
SimpleInjector
I have an Engine class which needs NotificationHub and NotificationHub needs ClientWriter to work. I managed to setup dependencies but it seems that the Engine cannot directly use the hub. Later I found out that the hub engine is using is different than the one clients are being connected to. Here are my files:
Engine.cs
public class Engine
{
private readonly NotificationHub _hub;
private readonly Timer _timer;
public Engine(NotificationHub hub)
{
_hub = hub;
_timer = new System.Timers.Timer(1000);
_timer.Elapsed += (o, arg) =>
{
_timer.Enabled = false;
Inform(arg.SignalTime.ToString());
_timer.Enabled = true;
};
_timer.Start();
}
public void Inform(string str)
{
try
{
var ctx = GlobalHost.ConnectionManager.GetHubContext("notification");
ctx.Clients.All.Notify($"{str} ====="); //works why?
_hub.Inform(str); // does not work why?
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
NotificationHub.cs
public class NotificationHub : Hub
{
ClientWriter _writer;
public NotificationHub(ClientWriter writer)
{
_writer = writer;
}
public override Task OnConnected()
{
Console.WriteLine($"connected {Context.ConnectionId}");
Clients.All.Notify($"{Context.ConnectionId} has joined");
return base.OnConnected();
}
public DateTime Inform(string msg)
{
_writer.Received(msg);
return DateTime.Now;
}
}
ClientWriter.cs
public class ClientWriter
{
public void Received(string arg)
{
Console.WriteLine(arg);
}
}
HubActivator
class SimpleInjectorSignalRHubActivator : IHubActivator
{
Container _container;
public SimpleInjectorSignalRHubActivator(Container container)
{
_container = container;
}
public IHub Create(HubDescriptor descriptor)
{
return _container.GetInstance(descriptor.HubType) as IHub;
}
}
Program.cs
static void Main(string[] args)
{
var _container = new Container();
_container.RegisterSingleton<NotificationHub>();
_container.RegisterSingleton<Engine>();
_container.RegisterSingleton<ClientWriter>();
var options = new StartOptions();
options.Urls.Add("http://localhost:12345");
using (var server = WebApp.Start(options, (app) =>
{
var activator = new SimpleInjectorSignalRHubActivator(_container);
GlobalHost.DependencyResolver.Register(typeof(IHubActivator), () => activator);
GlobalHost.DependencyResolver.Register(typeof(NotificationHub), () => _container.GetInstance<NotificationHub>());
app.RunSignalR(new HubConfiguration { EnableDetailedErrors = true });
app.UseWelcomePage();
}))
{
var ctx = GlobalHost.ConnectionManager.GetHubContext("notification");
var engine = _container.GetInstance<Engine>();
engine.Inform("Engine has started");
Console.ReadLine();
}
}
What am I doing wrong. Why the Engine is not able to send to the clients

No clients available in SignalR Hub context

I am new to SignalR and am experimenting with setting up a self host service that I can use to send events to other applications. I am having a problem getting a method to be called on all clients. In the code below _context.Clients.All is always empty. Any Ideas?
Test Method:
var server = new SignalRServer();
server.Start("http://localhost:8555");
var hubConnection = new HubConnection("http://localhost:8555");
var proxy = hubConnection.CreateHubProxy("testHub");
var executed = false;
proxy.On("testMethod", () =>
{
executed = true;
});
hubConnection.Start().Wait();
var hub = new TestHubContext(GlobalHost.ConnectionManager.GetHubContext<TestHub>());
hub.Test();
hubConnection.Stop();
server.Stop();
Self host server:
public class SignalRServer
{
private IDisposable _signalR;
public void Start(string url)
{
_signalR = WebApp.Start<SignalRStartup>(url);
}
public void Stop()
{
_signalR.Dispose();
}
}
public class SignalRStartup
{
public static IAppBuilder App = null;
public void Configuration(IAppBuilder app)
{
app.UseCors(CorsOptions.AllowAll);
app.MapSignalR();
}
}
Hub:
public class TestHub : Hub
{
}
public class TestHubContext
{
private IHubContext _context;
public TestHubContext(IHubContext context)
{
_context = context;
}
public void Test()
{
if (_context != null)
{
// At this poing _context.Clients.All is always empty
_context.Clients.All.testMethod();
}
}
}
I think your context / client connections are fine. Without further information I'm guessing your problem is that you are closing your connection and server too quickly after calling hub.Test()
Comment out the two .Stop() calls (and stick a Console.ReadLine in there to keep the console open) and see what happens

Categories

Resources