Dependency Inversion in Custom MVC Authorization Attributes
The Challenge
Given that we have a .NET C# MVC application that
does not leverage an implementation of a RoleProvider
(see here)
and relies on a persistent store for role assignments, how do we keep controller actions
restricted to only authorized users?
Authorization Attributes
The System.Web.MVC.AuthorizeAttribute
can generally be used when using
out-of-the-box role providers to decorate controllers or action methods to provide the
roles, users, etc. allowed to access the respective class and/or method:
[Authorize(Roles = "View")]
public class HomeController : Controller
{
[Authorize(Roles = "Admin")]
public ActionResult Index()
{
return View();
}
}
We can write custom implementations of the AuthorizeAttribute
to perform
our own business logic, or to access a persistent store for checking user role assignments.
Dependency Inversion, Injection, and Resolution
Dependencies
Our HomeController
depends on an IMessageService
to provide some
message to the Index
view – contrived, maybe, but this extends to other
services as necessary.
public class HomeController : Controller
{
private readonly IMessageService _messageService;
public HomeController(IMessageService messageService)
{
_messageService = messageService;
}
public ActionResult Index()
{
ViewBag.InjectedMessage = _messageService.GetMessage();
return View();
}
}
Our IMessageService
interface definition:
public interface IMessageService
{
string GetMessage();
}
And our stub concretion:
public class MessageService : IMessageService
{
public string GetMessage()
{
return "This is a stub message.";
}
}
Similarly, our custom authorization attribute will depend on an IDependentService
to provide an answer to whether or not an action is allowable:
public interface IDependentService
{
bool IsAuthorized();
}
And it’s stub concretion:
public class StubService : IDependentService
{
public bool IsAuthorized()
{
return true;
}
}
The less trivial implementation of IDependentService
would itself be dependent
on a persistent store to look up user role memberships, perform other business logic, etc.
But for now, it’s enough to just get a service injected into the attribute.
Injection
In this particular case, we are handling dependency inversion within the application (primarily by parameterized constructor injection) with Ninject.
Within our Global.asax
, we derive from NinjectHttpApplication
rather
than System.Web.HttpApplication
:
public class MvcApplication : NinjectHttpApplication
{
// This is the standard Application_Start method
// of the Global.asax, but renamed to not hide
// the NinjectHttpApplication Application_Start method.
private void Application_Start_Base()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}
protected override void OnApplicationStarted()
{
base.OnApplicationStarted();
Application_Start_Base();
}
protected override IKernel CreateKernel()
{
var kernel = new StandardKernel();
kernel.Load(new PresentationModule());
return kernel;
}
}
Binding
We bind concretions within modules to avoid having a brobdingnagian set of
bindings in the future. (See this
Ninject wiki on modules.) For the presentation layer, we have a single PresentationModule
that derives from NinjectModule
, which can be broken apart
later into smaller modules if necessary.
public class PresentationModule : NinjectModule
{
public override void Load()
{
Bind<IMessageService>()
.To<MessageService>();
Bind<IDependentService>()
.To<StubService>();
}
}
Resolution
When we implement our custom AuthorizeAttribute
, it depends on the IDependentService
to provide the yes/no answer to our authorization question. Note that we are deriving
from System.Web.Mvc.AuthorizeAttribute
and not
System.Web.Http.AuthorizeAttribute
which is used in WebAPI.
It would be great to be able to use parameterized constructor injection to resolve
the dependency on IDependentService
:
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
private IDependentService _someService;
public CustomAuthorizeAttribute(IDependentService dependentService)
{
_someService = dependentService;
}
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var systemDecision = base.AuthorizeCore(httpContext);
var serviceDecision = _someService.IsAuthorized();
return true;
}
}
…but we then get into invalid attribute parameter types, the inability to pass an argument when we use the attribute as a decoration, and we can’t compile the project.
We do, however, have access to the static DependencyResolver
to help us
resolve the dependency (see this
Microsoft documentation). This does mean that we’re using the service locator anti-pattern,
but what other options do we really have?
public class CustomAuthorizeAttribute : AuthorizeAttribute
{
protected IDependentService SomeService =>
DependencyResolver.Current.GetService<IDependentService>();
protected override bool AuthorizeCore(HttpContextBase httpContext)
{
var systemDecision = base.AuthorizeCore(httpContext);
var serviceDecision = SomeService.IsAuthorized();
return true;
}
}
Alternatively, we could decorate the IDependentService
property with Ninject’s
Inject
deocration to tell Ninject to resolve the property, but this tightly couples
our attribute to a specific IoC concretion. If we swap out from Ninject in the future,
this is just another place that we have to update.
[Inject]
protected IDependentService SomeService { get; set; }
Conclusion
By using an authorization attribute to limit access to controllers and methods, we are keeping with .Net MVC conventions – while at the same time we are straying from convention by using our own persistent role storage outside the context of ASP.Net roles.
We build our custom authorization attribute to depend on a business service to provide
additional authorization information. While parameterized constructor injection is a
feasible implementation of dependency resolution in controllers and some other objects, we
are forced to search out another solution for objects deriving from AuthorizeAttribute
.
We do want to avoid using the service locator “anti-pattern” when we have other options like constructor injection that more clearly indicate object dependencies. However, since that is not an option in some cases, the “anti-pattern” can become a valid pattern assuming its use is limited and well documented.