On a flight from Rome to Warsaw: apparently the droning noise of the plane (or what's left of it after this) inspires me, and now I finally have the means of pulling out live writer from a pocket and start writing.
This time I'd like to explore with you some further consequences of the shift toward claims, and specifically some novel ways of thinking about authorization. The seeds of this discussion are already in the Tao of Claims, but its sheer length makes them accessible only to the very patient reader 🙂
If you take the time to have a chat with somebody involved in writing software that deal with authorization, you'll likely discover they are driven by 2 main tropisms: 1) stopping unauthorized calls as early as possible in the invocation pipeline and 2) empowering as much as possible the infrastructure guys to specify authorization policies as deployment time options. Both are perfectly sound principles, rooted in the reality of enterprise life: you want to consume as little resources as possible, and you want to be able to translate the company caste system of roles & groups in actual privileges in resource handling.
IMHO, however, the view of authorization that those heuristics imply is somewhat crippled and does not exploit the claims system to its full potential. My point is basically rooted on two basic consideration: a) the outcome of an authorization operation is not necessarily just a boolean "yes you can call"/"no you can't call this method"; and b) the logic with which authorization is given or denied is often specific to the logic of the method itself, hence better handled by the method author. Let's get a closer look to those ideas.
Authorization is not always a 1/0 decision
You are calling a web service exposed by HR; the results will contain private information about a set of employees, namely all the ones currently involved in a certain project. If you work in the HR department, you have solid business reasons for accessing those data: the call will succeed and the information will handsomely unfold through your data grid. Let's assume, however, that you don't work in HR; you are in fact a managing consultant, in the professional services department of your company. In a simplistic view of the world, your call should just fail and hasta la vista baby. Let's make a post mortem of the failed call. The invokation should have returned data about people working on a certain project: namely Mario Rossi, Luigi Verdi and Kazumi Watanabe. You have actually no business reasons for knowing anything about Luigi and Kazumi-san: Mario, however, happens to be one of your reports (as managing consultant, you have a team of junior consultants reporting to you). You have very solid reasons for knowing about Mario's data; in fact, you need that information. The fact that calls to that method systematically fail when originated from professional services is the hidden reason for which the company replicates the functionality in another service, specifically made for working with a LOB application for the consultants. So much for reuse, capability consolidation & reaping the benefits of service orientation.
How to improve the situation? The trivial solution is refactoring the method, so that it works on a by person basis: instead of GetRating("project gnagnafru"), you'd make three calls GetRating(projectname, personname). In that case two calls would fail and one would succeed, so you'd get your information anyway. Unfortunately, the trivial solution is actually a non-solution. Besides the inelegance of increasing the chattyness of the system, you may simply not know who work in project gnagnafru in the first place! No, the solution is somewhat simpler: the output of the method must depend not only from the input required by the function signature, but from the call context as well. In this case, the call context includes your identity of managing consultant; so if you call GetRating for project gnagnafru you'll get just Mario's info, while a call from an HR employee would return the data of the entire crew of three people. In other words, authorization considerations influenced the outcome of the call rather than granting/denying access altogether.
This is actually a very simple case, in which the use of an authorization continuum is driven by a mismatch in granularity: ultimately the authorization check has still a 0/1 outcome, it is simply applied to sub-resources rather than to the method itself. You can devise cases in which the authorization operation has truly non-binary outcome; just think of the different levels of (the same) service you can get if you are gold, silver or bronze frequent flyer.
Infrastructure guys cannot take informed decisions about everything
W!hen it's a matter of expressing caste privileges, deployment time is the best moment for establishing your authorization rules. However there are a number of authorization decisions that are intimately correlated to the domain specific logic implemented by the function called, decisions for which the deploy guy is not well equipped to weight in. The same application server may host services designed to perform Radon transforms (authorizing the booting of extra virtual servers), serving images (authorizing content inclusion according to age & country) and providing shopping cart functions (authorizing expenses and applying discounts according to a complex mix of call data & context info). How can we expect the authorization rules fore those functions to be set by the same people?
Many of those decisions can be easily made inside the method body, while extracting those decisions so that they'll live in the pipeline before the method itself is awkward. If it's so awkward, why not only we do it routinely but we even consider it best practices? IMHO, there are 2 historical reasons that given today's tools are no longer valid:
- It's not easy to deliver context information to the method logic
That may have been true before the advent of claims. Now if I want to make a decision inside my method driven by the2 caller's age I can simply refer to the age claim. I am not imposing token format, nor I have any dependency on the technology of the transports used for the service deployment; the claim is the ultimate decoupling device. I am just referring to a business concept that is relevant for my decision (the age), I don't care at all about how the system managed to obtain its value. Neat!
- Rules change frequently, so they should not be hardcoded in the method
Very true, hardcoding something would be silly. However the place for making certain decisions is still the method and the best people to work them out are the method authors and the biz analysts behind them: the solution is not extracting that logic and putting it somewhere else, rather it is using the right tool for implementing that kind of functions. I am naturally talking about workflows & rules: in that case I retain the power of making changes, even at deployment time, AND I keep the control in the hand of who understands the domain specific concepts entailed in the function. Bottom line: while I want to be able to adjust the spending limits and the special offers rules without recompiling the method, and workflow gives me just that freedom, I don't see why I should be forced to express those requirements elsewhere (excluding the optimization argument, of course).
Confusing? Think of a simpler example. I have a method that serve images. For certain images the method will have to check the age of the caller: somebody below 18 should not see them at all, somebody between 19 and 20 should see them with black rectangles strategically placed here and there, anybody 21 above should be able to see them in all their splatter and gore (what were you thinking?;)). You don't know if the image is one of the bad ones until you execute at least few lines of the method, and the authorization decision you'll take is more complex than a simple grant/deny call rights altogether.
Authorization is a continuum
Traditional authorization product providers are right to try stopping unauthorized calls asap, and in giving to their customer maximum flexibility; this is especially true in the enterprise and wherever resources are under the control of strong & unambiguous governance. However the authorization is finer grained, and the shift toward claims (plus other advancements, such as the wholesale availability of workflow paradigm to all level of developers) puts back on the table more natural & integrated ways of handing authorization decisions. Looking forward to play with a generation of tools that will take advantage of this!
[update: after 2 days in Poland without connectivity, I'm back in Rome. Since my flight from Genova has been canceled, I am getting bored in the lounge & fiddling w the Internet: the best moment for finalizing the post, since I don't dare to open my mailbox :-)]