dsuryd / dotNetify

Simple, lightweight, yet powerful way to build real-time web apps.
https://dotnetify.net
Other
1.17k stars 164 forks source link

Force Authentication for VM (Windows Auth) #167

Closed marknitek closed 5 years ago

marknitek commented 5 years ago

I use dotnetify for an intranet application therfore we would like to windows auth. I managed to get it working by disallowing anonymous access:

    "windowsAuthentication": true,
    "anonymousAuthentication": false,

And use a CustomAuthorizeFilter for check for AD Role Membership for certain views

So far so good. But the app has an api too and that api should allow for anonymous access. Therefore i cannot use the anonymousAthentication:false setting. What i would need is to require athentication for all dotnetify/signalr hubs, so that CustomAuthorizeFilter can work. At the moment authentication does not happen if anonymous access is allowed.

In classic SignalR there there seems to be a solution for that : https://docs.microsoft.com/en-us/aspnet/signalr/overview/security/hub-authorization

GlobalHost.HubPipeline.RequireAuthentication();

But aspnet core requires the Authorize Attribute on the Hub. So what should i do in dotnetify?

Best thing would be to require authentication (princiapal information) for all dotnetify vms and then i could use custom authorize attribute to allow/disallow the user.

dsuryd commented 5 years ago

There's no easy answer here without SignalR Core providing API to programmatically control hub's authentication. I think you are correct that the best thing would be to push the authorization check to the VM level.

BTW, there's an [AllowAnonymous] attribute for web controllers; maybe you can use that to override the auth requirement for your API.

marknitek commented 5 years ago

Unfortunatly AllowAnonymous Attribute does not work when its disallowed on the iis/httpsys config. At least that is what i experienced. I read somwhere in the docs that its ignored in that situation...

So does that mean there is no way to fix this? Could the Authorize Attribute be injected to the hub? I guess there is some sort of hub hidden behind the vm?

dsuryd commented 5 years ago

I don't think it's a good idea for this library to include Authorize attribute at the hub level. Writing an authorization middleware would be just as effective, and possibly is the best solution.

marknitek commented 5 years ago

Ok so came up with a custom middleware solution based on your advise Since this is all new to me (aspnet core, middleware, etc) i would appreciate any issues you may see here.


    public class PreventAnonymousMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly IOptions<PreventAnonymousMiddlewareOptions> _options;

        public PreventAnonymousMiddleware(RequestDelegate next, IOptions<PreventAnonymousMiddlewareOptions> options)
        {
            _next = next;
            _options = options;
        }

        public async Task Invoke(HttpContext context, ILogger<PreventAnonymousMiddleware> logger)
        {
            var excludes = _options.Value.Excludes;
            if (excludes == null || excludes.All(exclude => !context.Request.Path.StartsWithSegments(exclude)))
            {
                var authenticated = context.User?.Identity?.IsAuthenticated;
                if (authenticated.HasValue && !authenticated.Value)
                {
                    context.Response.StatusCode = 401; //UnAuthorized
                    await context.Response.WriteAsync("Un Authorized");
                    return;
                }
            }

            await _next(context);
        }
    }

    public class PreventAnonymousMiddlewareOptions
    {
        public string[] Excludes { get; set; }
    }

    #region ExtensionMethod
    public static class PreventAnonymousMiddlewareExtension
    {

        public static IServiceCollection AddPreventAnonymous(this IServiceCollection service, Action<PreventAnonymousMiddlewareOptions> options = null)
        {
            options = options ?? (opts => { });

            service.Configure(options);
            return service;
        }

        public static IApplicationBuilder UsePreventAnonymous(this IApplicationBuilder app)
        {
            app.UseMiddleware<PreventAnonymousMiddleware>();
            return app;
        }
    }
    #endregion

Usage like this:

services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddPreventAnonymous(options => options.Excludes = new[] {"/api"});

Placement of middleware was key here. I had to call it at the beginning to prevent api middleware from taking over responsibility:

app.UseAuthentication();
app.UsePreventAnonymous();
dsuryd commented 5 years ago

You can also write dotnetify middleware if you only want to target the hub.