I am implementing a RESTful service that has a security model requiring authorization at three levels:
Implementing resource-level authorization is straight-forward; however, the other two are not. I believe the solution for instance-level authorization will reveal itself with a solution to the (harder, imo) property-level authorization question. The latter issue is complicated by the fact that I need to communicate the authorization decisions, by property, in the response message (ala hypermedia) - in other words, this isn't something I can simply enforce in property setters.
With each request to the service, I must use the current user's information to perform these authorization checks. In the case of a GET request for either a list of resources or an individual resource, I need to tell the API layer which attributes the current user can see (is visible) and whether the attribute is read-only or editable. The API layer will then use this information to create the appropriate response message. For instance, any property that is not visible will not be included in the message. Read-only properties will be flagged so the client application can render the property in the appropriate state for the user.
Solutions like application services, aspects, etc. work great for resource-level authorization and can even be used for instance-level checks, but I am stuck determining how best to model my domain so the business rules and checks enforcing the security constraints are included.
NOTE: Keep in mind that this goes way beyond role-based security in that I am getting the final authorization result based on business rules using the current state of the resource and environment (along with verifying access using the permissions granted to the current user via their roles).
How should I model the domain so I have enforcement of all three types of authorization checks (in a testable way, with DI, etc)?
Initial Assumptions
I suppose the following architecture:
Stateless Scaling Sharding
. .
. .
. .
+=========================================+
| Service Layer |
+----------+ | +-----------+ +---------------+ | +------------+
| | HTTP | | | | | | Driver, Wire, etc. | |
| Client | <============> | RESTful | <====> | Data Access | <=======================> | Database |
| | JSON | | Service | DTO | Layer | | ORM, Raw, etc. | |
| | | | | | | | | |
+----------+ | +-----------+ +---------------+ | +------------+
+=========================================+
. .
. .
. .
Initially, let us suppose you are authenticating the Client
with the Service Layer
and obtain a particular token, that encodes the authentication and authorization information in it.
First of all, what I first think of is to process all the requests and then only filter them depending on authorization. This will make the whole thing much simpler and much easier to maintain. However, of course there might be some requests requiring expensive processing, in which case this approach is absolutely not effective. On the other hand, the heavy-load requests will most probably involve resource level access, which as you have stated is easy to organise, and it is possible to detect and authorise in Service Layer
at API
or at least Data Access
levels.
Further Thoughts
As for instance and property level authorization, I will not even try to put it into the Data Access Layer
and would completely isolate it beyond the API level, i.e. starting from Data Access Layer
no any layer would even be aware of it. Even if you request a list of 1M objects and want to emit one or two properties from all objects for that particular client, it would be preferable to fetch the whole objects and then only hide the properties.
Another assumption is that your model is a clear DTO
, i.e. simply a data container, and all the business logic is implemented in the Service Layer
, particularly API
level. And say you pass the data across HTTP encoded as JSON
. So anyway, somewhere in front of the API
layer, you are going to have a small serialization stage, to transform your model into JSON
. So this stage is where I think is the ideal place to put the instance and property authorization.
Suggestion
If it comes to the property level authorization, I think there is no reasonable way to isolate the model from security logic. Be it a rule-based, role-based or whatever-based authorisation, the process is going to be verified upon a piece of value from the authentication/authorisation token provided by the Client
. So, in the serialisation level, you will get basically two parameters, the token and the model, and accordingly will serialise appropriate properties or the instance as a whole.
When it comes to defining the rules, roles and whatevers per property for the model, it can be done in various ways depending on the available paradigms, i.e. depending on the language the Service Layer
will be implemented. The definitions can be done heavily utilising Annotations (Java) or Decorators (Python). For emitting specific properties, Python will come handy with its dynamic typing and hacky features, e.g. Descriptors. In case of Java, you might end up encapsulating properties into a template class, say AuthField<T>
.
Summary
Summing all up, I would suggest to put the instance and property authorization in front of the API Layer
in the stage of serialisation. Thus basically, the roles/rules will be assigned in the model and the authorisation will be performed in the serialiser, being provided the model and token.
Since a comment was added recently, I thought I'd update this post with my learnings since I originally asked the question...
Simply put, my original logic was flawed and I was trying to do too much in my business entities. Following a CQRS approach helped make the solution clearer.
For state changes, the "write model" uses the business entity and a "command handler"/"domain service" performs authorization checks to make sure the user has the necessary permissions to update the requested object and, where applicable, change specific properties of that object. I still debate whether the property-level checks belong inside the business entity methods or outside (in the handler/service).
In the case of the "read model", a "query handler"/"domain service" checks the resource- and instance-level authorization rules so only objects to which the user has access are returned. The handler/service uses a mapper object that applies the property-level authorization rules to the data when constructing the DTO(s) to return. In other words, the data access layer returns the "projection" (or view) regardless of authorization rules and the mapper ignores any properties to which the current user does not have access.
I dabbled with using dynamic query generation based on the list of fields to which the user has access but found that to be overkill in our case. But, in a more complex solution with larger, more complex entities, I can see that being more viable.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With