Skip to content

Instantly share code, notes, and snippets.

@RickStrahl
Last active October 2, 2017 00:22
Show Gist options
  • Save RickStrahl/32c4366e1aa714f0262bba63b5cfb0bd to your computer and use it in GitHub Desktop.
Save RickStrahl/32c4366e1aa714f0262bba63b5cfb0bd to your computer and use it in GitHub Desktop.
Adding Authorization to a Generic Controller defined in an imported assembly

In my component I publish a UI component that uses an API controller to handle updating of application resources generically. There is a base .NET library that provides core localization services and an ASP.NET Core specific library that provides features specifically for ASP.NET.

The issue is how to allow the API Controller - which lives inside of the generic component and is not touched by the application directly - to be secured easily.

I ended with the following code:

services.AddWestwindGlobalization(opt =>
{                
    // Resource Mode - Resx or DbResourceManager                
    opt.ResourceAccessMode = ResourceAccessMode.DbResourceManager;  // ResourceAccessMode.Resx

    opt.ConnectionString = config.applicationConnectionString; opt.ResourceTableName = "localizations";
    
    ...
    
    <!-- THIS --->
    // Set up security for Localization Administration form
    opt.ConfigureAuthorizeLocalizationAdministration(controllerContext =>
    {
        // return true or false whether this request is authorized
        return controllerContext.HttpContext.User.Identity.IsAuthenticated;
    });
});

This is pretty straight forward to implement for the end user, but the internals are ugly because the base .NET Component doesn't have access to the Controller.

public class DbResourceConfiguration {
    
    ... 
    
    /// <summary>
    /// Internally used handler that is generically set to execute authorization
    /// when accessing the Localization handler
    /// </summary>    
    public object OnAuthorizeLocalizationAdministration = null;
}

The handler is meant to be Func<ControllerContext,bool> rather than object, but the configuration object doesn't have any access to ASP.NET Core functionality - so object to hold.

Then add an extension method to configuration

public static class DbResourceConfigurationExtensions
{

    public static void ConfigureAuthorizeLocalizationAdministration(this DbResourceConfiguration config,
        Func<ControllerContext, bool> onAuthorizeLocalizationAdministration)
    {
        config.OnAuthorizeLocalizationAdministration = onAuthorizeLocalizationAdministration;
    }
}

Finally this code is fired as part of the API controller:

public override void OnActionExecuting(ActionExecutingContext context)
{
    base.OnActionExecuting(context);
    
    if (DbIRes.Configuration.OnAuthorizeLocalizationAdministration != null)
    {
        var func = DbIRes.Configuration.OnAuthorizeLocalizationAdministration as Func<ControllerContext, bool>;
        if  (func != null)
        {
            if (!func.Invoke(ControllerContext))         
                // exception filter provides 401 JSON Error response
                throw new UnauthorizedAccessException();                    
        }
    }

}

which gives me:

HTTP/1.1 401 Unauthorized
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Date: Mon, 02 Oct 2017 00:08:32 GMT
Server: Kestrel

{
  "message": "Attempted to perform an unauthorized operation.",
  "isCallbackError": true,
  "errors": [],
  "data": null
}

when not authorized, or the actual response if they are.

Comments?

I'm happy with the way this works for the user, just providing a Func<ControllerContext,bool>. But the implementation is a bit hacky, especially with how the control context is passed to the Func<T> and how it's mapped to the configuration as object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment