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
.