I had an interesting question when sitting with a client today. The Event Store supports internally role based security through ACLs they would prefer to use a claim based system with it. An interesting idea, is there a reasonably easy way to do this? Well yes but it requires a bit of coding around (would be a nice thing to have in a library somewhere *wink wink*)
The general idea with claims based security is that something else will do the authentication and the application will act only on a series of claims that are given. In this particular example they want to control access to streams based upon claims about the user and to do it in a reasonably generic way.
As an example for a user you may receive the following claims.
{
organization : 37,
department : 50,
team : 3,
user : 12
}
What they want is to be able to use these in conjunction with streams to determine whether or not a given user should have access to the stream (and to be reasonably dynamic with it.)
Obviously we will not be able to easily do this with the internal security (well you could but it would be very ugly) but it can be built relatively easily on top. It is quite common to for instance run Event Store only on localhost and to only expose a proxy publicly (this kind of thing can be done in the proxy, while not an ideal solution it can get us pretty close to what we want.)
If we just want to work with say a single claim “can read event streams” we could simply do this in the proxy directly and check the claim before routing the request. Chances are however you want to do quite a bit more with this and make it more dynamic however which is where the conversation went in particular what about per stream and per stream-type setup dynamically? Well we could start using the stream metadata for this.
For a resource (stream metadata)
{
organization : 37,
department : 50,
team : 13,
user : 121
}
Now we could try taking the intersection of this with the claims provided on the user.
The intersection would result in
{
organization : 37,
department : 50,
}
We might include something along the lines of
{
approve = "organization,department",
somethingelse = "organization,department,team"
delete = "user"
}
Where the code would then compare the intersection to the verb you were trying to do (must have all). This is a reasonably generic way of handling things but we can one step further and add in a bit more.
/streams/account-defaultsetting
{
approve = "organization,department",
somethingelse = "organization,department,team"
delete = "user"
}
This is now defined in a default which will be merged with the stream metadata (much like how ACLs work). If a value is provided in the stream metadata it will override the default (based on type of stream). This allows us to easily setup defaults for streams as well. The logic in the proxy is roughly as follows:
Read {type}-defaultsetting (likely cached)
Read streammetadata
Merge streammetadata + {type-defaultsetting} to effective metadata
Calculate intersection of effective metadata with user info
Check intersection vs required permission for operation
This provides a reasonably generic solution that is quite useful in many circumstances. The one issue with it is that if someone roots your box they can access the data directly without permissions as they can bypass the proxy and talk directly on localhost (to be fair your probably have bigger problems at this point). It is however a reasonable solution for many situations.