Hangfire Dashboard authorization ends up in infinite loop - c#

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?

Related

Msal authentication hosted in AAD B2C doesn't auto refresh token after update from 3.1 to 5.0

Everything works fine as long as you don't use our application for more than 55 minutes after logging in without refresh. After that the customer is unable to communicate with our api's. I have it configured like this:
Portal Azure sign in flow:
Screenshot sign in flow properties
Program.cs
builder.Services.AddMsalAuthentication<RemoteAuthenticationState, CustomUserAccount>(options =>
{
builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("https://test.onmicrosoft.com/61111111e-1111-1111-bbbb-111111111111/API.access");
}).AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, CustomUserAccount, UserFactory>();
builder.Services.AddHttpClient("Test.Api", client => client.BaseAddress = new Uri(baseUrl)).AddHttpMessageHandler<TestAuthorizationMessageHandler>();
builder.Services.AddHttpClient<CustomerOrdersClient>(client => client.BaseAddress = new Uri(baseUrl)).AddHttpMessageHandler<TetsAuthorizationMessageHandler>();
AuthorizationMessageHandler:
public class TestaAuthorizationMessageHandler : AuthorizationMessageHandler
{
public TestAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigationManager) : base(provider, navigationManager)
{
ConfigureHandler(authorizedUrls: new[] { "https://localhost:5001", "https://localhost:44360", "https://app.test.nl", "https://test-api-prod-aa.azurewebsites.net" });
}
}
The client I use for this test. It works fine as long as the accesstoken doesnt expire.
public class ShipmentsClient : IClient<Shipment>
{
private readonly HttpClient _client;
private readonly string _endpoint = " api/shipments";
public ShipmentsClient(HttpClient client)
{
_client = client;
}
public async Task<List<Shipment>> GetAsync()
{
var shipments = new List<Shipment>();
try
{
shipments = await _client.GetFromJsonAsync<List<Shipment>>(_endpoint);
}
catch(Exception ex)
{
shipments = new List<Shipment>();
}
return shipments;
}
}

How do I configure Owin to use a custom AuthenticationHandler?

I have read that it's possible to create a custom Owin authentication handler, but I can't figure out how to configure Owin to use my handler instead of the default one.
How do I tell Owin to use this class instead of the default?
public class XDOpenIdAuthHandler: OpenIdConnectAuthenticationHandler
{
public XDOpenIdAuthHandler(ILogger logger)
: base(logger)
{
}
protected override void RememberNonce(OpenIdConnectMessage message, string nonce)
{
//Clean up after itself, otherwise cookies keep building up until we've got over 100 and
// the browser starts throwing errors. Bad OpenId provider.
var oldNonces = Request.Cookies.Where(kvp => kvp.Key.StartsWith(OpenIdConnectAuthenticationDefaults.CookiePrefix + "nonce")).ToArray();
if (oldNonces.Any())
{
CookieOptions cookieOptions = new CookieOptions
{
HttpOnly = true,
Secure = Request.IsSecure
};
foreach (KeyValuePair<string, string> oldNonce in oldNonces)
{
Response.Cookies.Delete(oldNonce.Key, cookieOptions);
}
}
base.RememberNonce(message, nonce);
}
}
You must add it as a part of a custom AuthenticationMiddleware.
public class CustomAuthMiddleware : AuthenticationMiddleware<OpenIdConnectAuthenticationOptions>
{
public CustomAuthMiddleware(OwinMiddleware nextMiddleware, OpenIdConnectAuthenticationOptions authOptions)
: base(nextMiddleware, authOptions)
{ }
protected override AuthenticationHandler<OpenIdConnectAuthenticationOptions> CreateHandler()
{
return new XDOpenIdAuthHandler(yourLogger);
}
}
Then using it in the Startup.Auth for example:
public partial class Startup
{
// For more information on configuring authentication, please visit https://go.microsoft.com/fwlink/?LinkId=301864
public void ConfigureAuth(IAppBuilder app)
{
app.Use<CustomAuthMiddleware>(new OpenIdConnectAuthenticationOptions());
}
}
Be aware however that the Owin pipeline must not contain the default OpenIdConnectAuthenticationMiddleware, otherwise it will still get called as part of the request pipe.

Authorization for anonymous user (automatic authentication)

UPDATE: Unfortunately, a Windows reboot solved this issue -.-
In our ASP.NET Core (1.0 RC2) application, we have the following requirement: only users from the internal network should be able to access some "Debug" pages (hosted by MVC Core). It's a public website and we don't have user logins, instead we managed it until now with a custom IP-address based authorization (note: this is not a security risk in our case, because we have a proxy in between, so the IP address cannot be spoofed from outside).
We want to implement such an IP-address based authorization in ASP.NET Core, as well. We use a custom policy "DebugPages" for this and corresponding [Authorize(Policy="DebugPages")] definitions on the MVC controller. Then we noticed, that we must have an authenticated user to get the AuthorizeAttribute to jump in and we create one in the request pipeline, which yields to the following code in Startup.cs (shortened for brevity):
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthorization(options =>
{
options.AddPolicy(
"DebugPages",
policy => policy.RequireAssertion(
async context => await MyIPAuthorization.IsAuthorizedAsync()));
});
}
public void Configure(IApplicationBuilder app)
{
...
app.Use(async (context, next) =>
{
context.User = new ClaimsPrincipal(new GenericIdentity("anonymous"));
await next.Invoke();
});
...
}
Now this works fine when run in Debug by Visual Studio 2015 (with IIS Express).
But unfortunately it doesn't work when run directly by dotnet run (with Kestrel) from the command line. In this case we get the following exception:
InvalidOperationException: No authentication handler is configured to handle the scheme: Automatic
The same error occurs when we provide the current Windows principal instead of the principal with a custom anonymous identity -- so everytime when the user is automatic-ally authenticated...
So, why is there a difference between hosting in IIS Express and Kestrel? Any suggestions how to solve the issue?
So, after some research, as I mentioned in the comments, I have found that httpContext.Authentication.HttpAuthhenticationFeature.Handler is null, when you starts the application under the "selfhosted" kestrel. But when you use IIS the Handler has instantiated by Microsoft.AspNetCore.Server.IISIntegration.AuthenticationHandler. This specific handler implementation is part of the .UseIISIntegration() in Program.cs.
So, I've decided to use a part of this implementation in my App and handle nonauthenticated requests.
For my WebAPI (without any Views) service I use IdentityServer4.AccessTokenValidation that uses behind the scenes OAuth2IntrospectionAuthentication and JwtBearerAuthentication.
Create files
KestrelAuthenticationMiddleware.cs
public class KestrelAuthenticationMiddleware
{
private readonly RequestDelegate _next;
public KestrelAuthenticationMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext context)
{
var existingPrincipal = context.Features.Get<IHttpAuthenticationFeature>()?.User;
var handler = new KestrelAuthHandler(context, existingPrincipal);
AttachAuthenticationHandler(handler);
try
{
await _next(context);
}
finally
{
DetachAuthenticationhandler(handler);
}
}
private void AttachAuthenticationHandler(KestrelAuthHandler handler)
{
var auth = handler.HttpContext.Features.Get<IHttpAuthenticationFeature>();
if (auth == null)
{
auth = new HttpAuthenticationFeature();
handler.HttpContext.Features.Set(auth);
}
handler.PriorHandler = auth.Handler;
auth.Handler = handler;
}
private void DetachAuthenticationhandler(KestrelAuthHandler handler)
{
var auth = handler.HttpContext.Features.Get<IHttpAuthenticationFeature>();
if (auth != null)
{
auth.Handler = handler.PriorHandler;
}
}
}
KestrelAuthHandler.cs
internal class KestrelAuthHandler : IAuthenticationHandler
{
internal KestrelAuthHandler(HttpContext httpContext, ClaimsPrincipal user)
{
HttpContext = httpContext;
User = user;
}
internal HttpContext HttpContext { get; }
internal ClaimsPrincipal User { get; }
internal IAuthenticationHandler PriorHandler { get; set; }
public Task AuthenticateAsync(AuthenticateContext context)
{
if (User != null)
{
context.Authenticated(User, properties: null, description: null);
}
else
{
context.NotAuthenticated();
}
if (PriorHandler != null)
{
return PriorHandler.AuthenticateAsync(context);
}
return Task.FromResult(0);
}
public Task ChallengeAsync(ChallengeContext context)
{
bool handled = false;
switch (context.Behavior)
{
case ChallengeBehavior.Automatic:
// If there is a principal already, invoke the forbidden code path
if (User == null)
{
goto case ChallengeBehavior.Unauthorized;
}
else
{
goto case ChallengeBehavior.Forbidden;
}
case ChallengeBehavior.Unauthorized:
HttpContext.Response.StatusCode = 401;
// We would normally set the www-authenticate header here, but IIS does that for us.
break;
case ChallengeBehavior.Forbidden:
HttpContext.Response.StatusCode = 403;
handled = true; // No other handlers need to consider this challenge.
break;
}
context.Accept();
if (!handled && PriorHandler != null)
{
return PriorHandler.ChallengeAsync(context);
}
return Task.FromResult(0);
}
public void GetDescriptions(DescribeSchemesContext context)
{
if (PriorHandler != null)
{
PriorHandler.GetDescriptions(context);
}
}
public Task SignInAsync(SignInContext context)
{
// Not supported, fall through
if (PriorHandler != null)
{
return PriorHandler.SignInAsync(context);
}
return Task.FromResult(0);
}
public Task SignOutAsync(SignOutContext context)
{
// Not supported, fall through
if (PriorHandler != null)
{
return PriorHandler.SignOutAsync(context);
}
return Task.FromResult(0);
}
}
And in the Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseMiddleware<KestrelAuthenticationMiddleware>();
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = Configuration[AppConstants.Authority],
RequireHttpsMetadata = false,
AutomaticChallenge = true,
ScopeName = Configuration[AppConstants.ScopeName],
ScopeSecret = Configuration[AppConstants.ScopeSecret],
AutomaticAuthenticate = true
});
app.UseMvc();
}

Dashboard Authorization (self-hosted as a Windows service)

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?

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