FubuMVC as an OSS project is still mostly dead, but we still use it very heavily at work and one of our teams asked me to explain how the authorization works. You’re probably never going to use FubuMVC, but we did some things that were interesting and successful from a technical perspective. Besides, it’s always more fun to learn from other people’s mistakes — in this case, my mistakes;)
Early in the project that originally spawned FubuMVC we spent almost two months tackling authorization rules in our application. We had a couple needs:
- Basic authorization to allow or deny user actions based on their roles and permissions
- Authorization rules based on the current business object state (rules like “an amount > 5,000 requires manager approval”).
- The ability to extend the authorization of the application with custom rules and exceptions for a customer without any impact on the core code
- Use authorization rules to enable, disable, show, or hide navigation elements in the user interface
The core architectural concept of FubuMVC was what we called the “Russian Doll Model” that allowed you to effectively move cross cutting into our version of middleware. Authorization was an obvious place to use a wrapping middleware class around endpoint actions. If a user has the proper rights, continue on. If the user does not, return an HTTP 403 and some kind of authorization failure response and don’t execute the inner action at all.
For some context, you can see the part of our AuthorizationBehavior class we used to enforce authorization rules:
protected override void invoke(Action action) { if (!_settings.AuthorizationEnabled) { action(); return; } var access = _authorization.IsAuthorized(_context); // If authorized, continue to the inner behavior in the // chain (filters, controller actions, views, etc.) if (access == AuthorizationRight.Allow) { action(); } else { // If authorization fails, hand off to the failure handler // and stop the inner behaviors from executing // the failure handler service was pluggable in fubu too var continuation = _failureHandler.Handle(); _context.Service<IContinuationProcessor>().Continue(continuation, Inner); } }
Pulling the authorization checks into a separate middleware independent of the inner HTTP action had a couple advantages:
- It’s easier to share common authorization logic
- Reversibility — meaning that it was easy to retrofit authorization logic onto existing code without having to dig into the actual endpoint action code.
- The inner endpoint action code could be simpler by not having to worry about authorization (in most cases)
FubuMVC itself might have failed, but the strategy of using composable middleware chains is absolutely succeeding as you find it in almost all of the newer HTTP frameworks and service bus frameworks including the new ASP.Net Core MVC code.
Authorization Rights and Policies
The core abstraction in FubuMVC’s authorization subsystem was the IAuthorizationPolicy interface that could be executed to determine if a user had rights to the current action:
public interface IAuthorizationPolicy { AuthorizationRight RightsFor(IFubuRequestContext request); }
The “AuthorizationRight” class above was a strong typed enumeration consisting of:
- “None” — meaning that the policy just didn’t apply
- “Allow”
- “Deny”
If multiple rules applied to a given endpoint, each rule would be executed to determine if the user had rights. Any rule evaluating to “Deny” automatically failed the authorization check. Otherwise, at least one rule had to be evaluated as “Allow” to proceed.
Being able to combine these checks enabled us to model both the simple, role and permission-based authorization, and also rules based on business logic and the current system state.
Simple Role Based Authorization
In the following example, we have an endpoint action that has a single “AllowRole” authorization rule:
// This action would have the Url: /authorized/hello [AllowRole("Greeter")] public string get_authorized_hello() { return "Hello."; }
Behind the scenes, that [AllowRole] attribute is evaluated once at application startup time and adds a new AllowRole object to the underlying model for the “GET /authorized/hello” endpoint.
For some context, the AllowRole rule (partially elided) looks like this:
public class AllowRole : IAuthorizationPolicy { private readonly string _role; public AllowRole(string role) { _role = role; } public AuthorizationRight RightsFor(IFubuRequestContext request) { return PrincipalRoles.IsInRole(_role) ? AuthorizationRight.Allow : AuthorizationRight.None; } }
Model Based Configuration
FubuMVC has an underlying model called the “behavior graph” that models exactly which middleware handlers are applicable to each HTTP route. Part of that model is an exact linkage to the authorization policies that were applicable to each route. In the case above, the “GET /authorized/hello” endpoint has a single AllowRole rule added by the attribute.
More powerfully though, FubuMVC also allowed you to reach into the underlying behavior graph model and add additional authorization rules for a given endpoint. You could do this through the marker attributes (even your own attributes if you wanted), programmatically if you had to, or through additive conventions like “all endpoints with a route starting with /admin require the administrator role.” We heavily exploited this ability to enable customer-specific authorization checks in the original FubuMVC application.
Authorization & Navigation
By attaching the IAuthorizationPolicy objects to each behavior chain, FubuMVC is also able to tell you programmatically if a user could navigate to or access any HTTP endpoint using the IEndpointService like this:
IEndpointService endpoints = runtime .Get<EndpointService>(); var endpoint = endpoints .EndpointFor<SomeEndpoint>(x => x.get_hello()); var isAuthorized = endpoint.IsAuthorized;
We used the IEndpointService (and a simpler one not shown here called IAuthorizationPreviewService) in server side rendering to know when to show, hide, enable, or disable navigation elements based on whether or not a user would have rights to access those routes. By decoupling the authorization rules a little bit from the actual endpoint action code, we were able to define the authorization rules exactly once for every endpoint, then reuse the same logic in navigation accessibility that we did at runtime when the actual resource was requested.
This ability to “preview” authorization rights for HTTP endpoints was also useful for hypermedia endpoints where you used authorization rights to include or exclude additional links in the response body.
Lastly, having the model of what authorization rules applied to each route enabled FubuMVC to be able to present diagnostic information and visualizations of the middleware configuration for each HTTP endpoint. That kind of diagnostics becomes very important when you start using conventional policies or extensibility mechanism to insert authorization rules from outside of the core application.
Rule Object Lifecycle
FubuMVC, especially in its earlier versions, is awful for the number of object allocations it makes at runtime. Starting with FubuMVC 2, I tried to reduce that overhead by making the authorization rule objects live through the lifecycle of the application itself instead of being created fresh by the underlying IoC container on every single request. Great, but there are some services that you may need to access to perform authorization checks — and those services sometimes need to be scoped to the current HTTP request. To get around that, FubuMVC’s IAuthorizationPolicy takes in an IFubuRequestContext object which among other information contains a *gasp* service locator for the current request scope that authorization rules can use to perform their logic.
There’s been an almost extreme backlash against any and all usages of service locators over the past several years. Most of that is probably very justified, but in my opinion, it’s still very valid to use a service locator within an object that has a much longer lifetime than the dependencies it needs to use within certain operations. And no, using Lazy<T> or Func<T> builders injected in at the original time of creation will not work without making a potentially harmful dependency on things like the HttpContext for the scoping to work.
Please don’t dare commenting on this post with any form of “but Mark Seemann says…” I swear that I’ll reach through the interwebs and smack you silly if you do. Probably make a point of never, ever doing that on any kind of StructureMap list too for that matter.
What I’d do differently if there’s a next time
For the past couple years we’ve kicked around the idea of an all new framework we’re going to call “Jasper” that would be essentially a reboot of a core subset of FubuMVC on the new CoreCLR and all the forthcoming ASP.Net Core goodies. At some point I’ve said that I wanted to bring over the authorization model from FubuMVC roughly as is, but the first step is to figure out if we could use something off the shelf so we don’t have to support our own custom infrastructure (and there’s always the possibility that we’ll just give up and go to the mainstream tools).
The single biggest thing I’d change the next time around is to make it far easier to do one-off authorization rules as close as possible to the actual endpoint action methods. My thought has been to have something like a convention or yet another interface something like “IAuthorized” so that endpoint classes could happily expose some kind of Authorize() : AuthorizationRight method for one off rules.
Honestly though, I’m content with how the authorization model played out in FubuMVC. A big part of the theoretical plans for “Jasper” is be much, much more conscious about allocating new objects (i.e., use the IoC container much less) and adopting an async by default, all the way through approach to the runtime model.
It may not support everything but I think something like [Identity Server](https://github.com/IdentityServer) might be have enough flexibility to build on top of rather than re-implementing something from scratch.
We’re going to look at it at some point. It’s not entirely a one for one analogue with what we did and use though.
Did you write somewhere your thoughts about DI pattern? It would be interesting to read.
I just had to visit this page after reading the article in my feed. I’m really only here to see if someone got slapped! I almost spit out my drink when I read your comment warning. Too funny.