36

I am working on a small application trying to grasp the principles of domain-driven design. If successful, this might be a pilot for a larger project. I'm trying to follow the book "Implementing Domain-Driven Design" (by Vaughn Vernon) and trying to implement a similar, simple discussion forum. I've also checked out the IDDD samples on github. I have some difficulties adopting the Identity and Access to my case. Let me give some background information:

  • I (hopefully) understand the reasoning behind separating the users and permissions logic: it is a supporting domain, and it's a different bounded context.

  • In the core domain, there are no users, just Authors, Moderators, etc. These are created by reaching out to the Identity and Access context using a service and then translating the received User objects to and Moderator.

  • Domain operations are called with a related role as a parameter: e.g.:

    ModeratePost( ..., moderator);

  • The method of the domain object checks if the given Moderator instance is not null (the Moderator instance will be null if the user asked from the Identity and Access context does not have the Moderator role).

  • In one case, it does an additional check before altering a Post:

    if (forum.IsModeratedby(moderator))

My questions are:

  • In the latter case aren't the security concerns blended again into the core domain? Previously the books states "with who can post a subject, or under what conditions that is permitted. A Forum just needs to know that an Author is doing that right now".

  • The role based implementation in the book is fairly straightforward: when a Moderator is the core domain tries to convert the current userId into a Moderator instance or into an Author when it needs that. The service will respond with the appropriate instance or a null if the user does not have the required role. However, I can't see how could I adapt this to a more complex security model; our current project I'm piloting for has a rather complex model with groups, ACLs, etc.

Even with rules that are not much very complex, like: "A post should be edited only by its Owner or an Editor", this approach seems to break down, or at least I don't see the correct way to implement it.

By asking the Identity and Access context for an OwnerOrEditor instance doesn't feel right, and I would end up with more and more security-related classes in the core domain. In addition, I would need to pass not just the userId, but the identifier of the protected resource (the id of the post, forum, etc.) to the security context, which probably should not care about these things (is it correct?)

By pulling the permissions to the core domain and checking them in the methods of the domain objects or in the services, I'd end up at square one: mixing security concerns with the domain.

I've read somewhere (and I tend to agree with it) that these permission related things should not be a part of the core domain, unless security and permissions are the core domain itself. Does a simple rule like the one given above justify making security a part of the core domain?

LittlePilgrim
  • 473
  • 1
  • 4
  • 8
  • Maybe you can find what you need here : https://stackoverflow.com/a/23485141/329660 Also, just because the Access Control context knows about a resource *ID* doesn't mean it has domain knowledge about what kind of entity that resource is or what it does. – guillaume31 Mar 14 '18 at 15:53
  • Thanks, I've seen that post earlier, my problem is exactly what the edit says at the end: I would like to move access control out of my core domain but I feel I've hit a wall with my implementation. However, your suggestion about the resource ID makes sense: as I don't use the concept of User or Role in the core domain but concrete roles, maybe I could use the concept of Resource in the security BC and map them to the related concrete domain concept. Worth a try, thanks! – LittlePilgrim Mar 14 '18 at 16:05
  • Don't the code samples in the link at least answer to *"I can't see how could I adapt this to a more complex security model"*? – guillaume31 Mar 14 '18 at 16:30
  • My problem is not with the implementation of the security model itself, I can't see how should I map these more complicated rules into the domain. How should the User -> Author mapping change if it is not a simple role based model on the security side? Passing resource IDs to the other context might work, like `HasPermissionToEdit(userId, resourceId)` but I doesn't feel right to contaminate the domain logic with these calls. Probably I should check these in the application service methods, before invoking the domain logic? – LittlePilgrim Mar 14 '18 at 16:49
  • Of course it should be in application services... I thought it was clear from parts of the code like `UserService @AccessControlList[inf3rno]` in the answer I linked to. – guillaume31 Mar 15 '18 at 08:10

3 Answers3

16

I have run into the same questioning and am posting the answer I have come up with because it might give some additional vision on the topic, even though the discussion took place a while ago (still second link on the topic in Google).

I think there are two different kinds of access controls at play:

  • Role validation in terms of authentication scheme
  • Action validation in business terms

Knowing if a particular user is a Moderator belongs to the Identity & Access bounded context. That's because it has to do with the rights the user has - its category.

But

Knowing if a moderator can edit a post belongs to the business domain, and should be out of the Identity & Access bounded context. A good way to see that is that the condition is expressed without referring to the targeted user: it is a business rule that a moderator can or cannot edit another moderator's post for example. It should not pollute the Identity & Access BC.

It becomes especially clear when you want to apply this reasoning to scopes in an OAuth setting. Now in addition to auth rules, you also have to authorize actions based on whether specific scopes are included or not in the token request. Where do you put that?

I'd say you put that in the business BC. That's because the scopes semantics are known to the business domain. Having a specific role and knowing what it allows to do are two different matters. First part is in the I&A BC, second part is in the business domain.

And it feels right: if suddenly you want to forbid topic edition by a moderator, you should have to modify the forum BC, not the I&A BC - nothing has changed to roles definition really, it's just their semantics in a specific BC that changed.

That's why I think it is the best solution to have the check if (forum.IsModeratedby(moderator)) in the business BC. I guess I disagree with the accepted answer on that matter.

If many rules like that exist for a specific business BC, it seems to me that the best way is to bundle them in a subdomain in the business BC. Business rules like that can benefit from being embodied as an object to call (like a Strategy pattern).

Qortex
  • 261
  • 2
  • 5
  • How would you recommend providing the user permissions/scopes from your application or API layer to your domain? A parameter for the domain method, perhaps? – rhyek Jul 21 '21 at 05:40
  • 1
    In my app, I put together a concept of `passport` issued by the I&A BC. The passport of the requesting user can be retrieved through an exposed I&A service and holds what I call `visas`. Visas are looked for and intepreted in the business BC depending on their semantics. I didn't implement scopes actually, but I would add them to the passport so that the business BC can get them when it needs to check for their presence. – Qortex Jul 21 '21 at 08:05
  • 1
    Are your visas self-contained objects the higher levels provide to the domain layer? I recently implemented something similar. Application layer generates an AuthorizationDomainObject (via an AuthorizationService). it implements an interface defined in the Domain Layer. this object contains relevant info (permissions, etc) that business logic can parse when needed. – rhyek Jul 22 '21 at 13:43
  • 1
    Yes, I understand it's indeed a similar approach. Pretty handy & extensible in my experience. – Qortex Jul 26 '21 at 19:20
  • Nice answer! About _"A good way to see that is that the condition is expressed without referring to the targeted user"_, does that mean a method `Post::isUpdatableByUser(User $user)` would be wrong? – Kwadz Apr 27 '22 at 02:46
  • If you don't have a specific BC that handles this, then it's acceptable. But your `Post` model will now incorporate lots of business logic. The more evolutive solution IMO is to have a `PostAuthorization` BC where you have `PostAuthorization::canUserUpdatePost(post, user)`. This applies the business logic and returns. That way, it is orthogonal to your post: changing the authorization business rules will not change your `Post` class. Bottom line is: it is not in the I&A BC, because it's a business rule expressed in business terms, I&A BC does not know the business rules. – Qortex Apr 27 '22 at 13:36
  • Thanks, do you think that a _specification_ would also be a good choice for this? – Kwadz May 03 '22 at 10:51
  • I don't know what a /specification/ is unfortunately :/ Good luck on that one :) – Qortex May 03 '22 at 16:10
  • If you're interested, the _specification_ pattern is pretty well explained in [this article](https://medium.com/@pawel_klimek/domain-driven-design-specification-pattern-82867540305c). – Kwadz Oct 26 '22 at 14:19
  • About authorisations, what do you think about `Moderator::updatePost(post)` using polymorphism for each type of user with the specific logic inside each `updatePost`? – Kwadz Oct 26 '22 at 14:21
15

Authentication and Authorisation is a bad example for DDD.

Neither of these things are part of a Domain unless your company creates security products.

The Business or domain requirement is, or should be, "I require role based authentication"

You then check the role before calling a domain function.

Where you have complex requirements such as 'I can edit my own posts but not others' ensure that your domain separates the edit function out into EditOwnPost() and EditOthersPost() so that you have a simple function to role mapping

You can also separate the functionality into Domain Objects, such as Poster.EditPost() and Moderator.EditPost() this is a more OOP approach, although your choice may depend on whether your method is in a Domain Service or a Domain Object.

However you choose to separate the code the Role mapping will occur outside of the Domain. so for example if you have a webapi controller:

PostController : ApiController
{
    [Authorize(Roles = "User")]
    public void EditOwnPost(string postId, string newContent)
    {
        this.postDomainService.EditOwnPost(postId, string newContent);
    }

    [Authorize(Roles = "Moderator")]
    public void EditOtherPost(string postId, string newContent)
    {
        this.postDomainService.EditOtherPost(postId, string newContent);
    }
}

As you can see although the role mapping is done on the hosting layer, the complex logic of what constitutes editing your own or an others post is part of the domain.

The domain recognises the difference of the actions, but the security requirement is simply that "functionality can be limited by roles".

This is perhaps clearer with the domain objects separation, but essentially you are checking the method which constructs the object instead of the method which calls the service method. Your requirement, if you still want to voice it as part of the domain would become 'only moderators can construct the moderator object'

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
Ewan
  • 70,664
  • 5
  • 76
  • 161
  • This answer is spot on. Authentication and Authorization are simply outside the concern for *most* DDD systems. I would only add here, that it may be preferable to, instead of just functionally mapping roles to different methods, create new Entities that are responsible for privileged actions (`Visitor` vs `RegisteredUser` vs `AdminUser` for a web-based example). Conceptually, I find this approach easier to understand. I also have found that it provides a cleaner separation of actions/validations. – user3347715 Mar 14 '18 at 16:33
  • I agree with you that Authentication and Authorization are outside the _core_ domain. However, they must be implemented somehow, as the are critical to business. I might use a prepurchased/open source component, but my problem is exactly this: how can I communicate with this supporting/generic subdomain without the concepts bleeding into the core domain? – LittlePilgrim Mar 14 '18 at 16:40
  • After the edits (and digesting the pieces) I think I'm starting to see your point. My question was more about where the role mapping/permission checking should happen. Author, Moderator, etc. are in the ubiquitous language, while User and Role are not: I need to do the mapping. If I'm not mistaken, you suggest doing this outside the domain objects and services, even before calling the service methods. On the other hand, wouldn't this (moving this code out of the application services) result in code duplication if there are multiple clients (e.g. a REST API and a web client?). – LittlePilgrim Mar 14 '18 at 19:08
  • you need to secure your services before even the code is executed, you also want to prevent your client making calls that you know will fail. So the server is mandatory, the client is optional. If you use a token with claims you can do both – Ewan Mar 14 '18 at 20:01
  • @Ewan I have to disagree with you. Your two examples do not offer any insight about what actually happens in your domain. Why would there be two methods that do the same thing "edit a post". In order to differentiate between the actions and teh conditions for that action to be fulfiled, you need to show in your domain who exactly does the action. Otherwise you would end up with the same statements inside the method's / action's body in your domain objects. – Geo C. May 15 '18 at 10:50
  • @geoc they dont, one edits only the users posts, one edits posts belonging to other users. the implementation will be similar to be sure, but not the same. the differnet methods will be mapped to different use cases. the exact limitation about who can use which method is left to the security implementation. outside the business logic domain – Ewan May 15 '18 at 10:55
  • 5
    Checking role "statically" is a little bit simplistic. What if a moderator is not allowed to edit the post of another moderator? Shouldn't this check be part of the domain? – Réda Housni Alaoui Dec 22 '18 at 11:41
  • 3
    @RédaHousniAlaoui I'm wondering about this as well. I can't think of a way to handle this other than either including some mention of Users/Moderators in the domain, or performing some kind of logic within that ApiController to get the post's author's role. Neither of these seems right, and this is a common enough sort of thing in my experience that some clear guidance would be extremely helpful. – Jimmy Mar 28 '19 at 14:22
  • You simply expose methods EditNormalUsersPost and EditModeratorsPost and limi to the appropriate roles – Ewan Mar 28 '19 at 14:54
  • @Erwan ,what if this authorization is dynamic? Imagine a UI allowing to define user group by configuration. Imagine also that the user can decide to let a group modify the posts of another group by configuration. In that case you can't create one method per possible combination. – Réda Housni Alaoui Mar 28 '19 at 15:10
  • it isnt dyanmic – Ewan Mar 28 '19 at 15:11
  • Please read my edited comment above :) – Réda Housni Alaoui Mar 28 '19 at 15:12
  • please read my answer to your comment above.. oh wait – Ewan Mar 28 '19 at 15:13
  • 4
    @Erwan, the use case I am talking about is dynamic. Basing the sentence "Authentication and Authorisation is a bad example for DDD" on hello world examples is dishonest. DDD is here to avoid accidental complexity and allow to manage domain complexity. Dynamic permissions is not an accidental complexity nor something that does not happen in real life. – Réda Housni Alaoui Mar 28 '19 at 15:16
  • 1
    The problem with the kind of dynamic role assignment you describe is that it leads to infinite roles. It's permissions based authentication. You can do it, but you end up with massive grids of checkboxes against users/permissions. You are better off sticking to Role based auth and defining your rule less dynamically with EditPostInAGroupImAMemberOf or whatever nearest approach you can get – Ewan Mar 28 '19 at 15:21
  • 3
    IMHO, the issue with your solution is that it does not satisfy the customer. The customer often want to be able to change those relations dynamically. Moreover, that's also what happens when a vendor provides the same enterprise software to different companies. If the software is poorly tweak-able, the vendor will eventually die. – Réda Housni Alaoui Mar 28 '19 at 15:27
  • It is possible that in some circumstances you will have to implement that kind of thing in order to meet a requirement. But it's generally recognised as a "bad thing" your security settings become unmanageable over time which effectively means your app becomes insecure. You are basically saying that the feature is more important than security. Which might be true, but is not generally good to assume – Ewan Mar 28 '19 at 15:33
  • 1
    "But it's generally recognised as a "bad thing" your security settings become unmanageable over time which effectively means your app becomes insecure." With the correct design and test, it is totally manageable. But, from my XP, to produce the correct design, the domain must check the permission. The alternative is utopia. – Réda Housni Alaoui Mar 28 '19 at 15:43
  • But let's explore the more static case with "You simply expose methods EditNormalUsersPost and EditModeratorsPost and limit to the appropriate roles". You have to check that the user is normal or moderator in the controller then? I feel that the controller should be as dumb as possible and should not be smart enough to know if user is a moderator or a normal user. Or do you do it using another method? – Réda Housni Alaoui Mar 28 '19 at 15:46
  • to take your groups that can edit groups example you have n^2 permissions. Sure you code can correctly apply them, but can a human check to see if someone in a group of bad actors can edit a group in the set of sensitive groups – Ewan Mar 28 '19 at 15:52
  • no the controller only knows if the post is a "normal" or "moderator" post – Ewan Mar 28 '19 at 15:52
  • How does the controller know it? – Réda Housni Alaoui Mar 28 '19 at 16:07
  • 1
    are we designing an entire social media app through a series of questions? – Ewan Mar 28 '19 at 16:13
  • 3
    I have no other way than ask questions to understand the design you are defending. If you don't want to answer questions, maybe you are on the wrong site :D – Réda Housni Alaoui Mar 28 '19 at 16:26
  • 1
    "Authentication and Authorisation is a bad example for DDD. Neither of these things are part of a Domain unless your company creates security products." This is a fair point, but it doesn't mean these things aren't _part_ of your business logic. It may be silly to do things like authentication or populating or user info/permissions/claims info in the domain layer (things that are common to every call), but I'm not sure why it would be so wrong to put authorization logic in your domain model or in a domain service especially if you're doing something like resource based authorization... – Jordan May 28 '19 at 16:46
  • @Jordan the problem is two fold. 1. its easy to be lead into reinventing the wheel, when role based auth is out of the box and can cover 99% of cases. and 2. security is cross cutting concern. If you embed it in a layer lower than the top most you can run into issues very quickly. – Ewan May 28 '19 at 18:10
  • @Ewan fair point on #1, but I'm not sure I'm convinced on #2 for resource based authorization where you have something like a resource, action and qualifier such that you would check, for instance if someone has the permissions to update (action) an order (action) only if it's their own (qualifier). You might have multiple service methods that call the same method on your domain model. Why should each of these services have to repeat the same authorization check when you could embed this check, as part of the business logic, in the model itself? Could you provide some counter examples? – Jordan May 28 '19 at 19:05
  • 1
    @Jordan the counter example for #2 is that you may well have an Export or other admin function which is supposed to bypasses all the security checks. But having embedded them in the Business logic you are unable to do so. – Ewan May 28 '19 at 19:07
  • @Ewan - This would come down to the qualifier check - e.g. either they need to have access to update “all” or “own” – Jordan May 28 '19 at 19:12
  • @Jordan in the case of the admin function there may not even be a user at all. Its going to be something completely outside your normal system, eg, back up data, calculate taxes owed, produce access log audit etc – Ewan May 28 '19 at 19:15
  • @Ewan this could be handled outside of the domain (when populating user info, claims/permissions, etc...) then your authorization method would check the context for a bypass – Jordan May 28 '19 at 19:51
  • @Jordan im not sure if you are suggesting a duplicate object without the auth or a backdoor in the auth which enables bypassing. obvs both are bad – Ewan May 28 '19 at 19:53
  • @Ewan I'm not suggesting either of those - I'm suggesting at a "higher" application layer, like in your middleware, you're doing your authentication and authorization. This is where you'll populate the user information and permissions or, if you have a special case for systems or admin users that bypass authorization checks, you would still authenticate them properly and add something to the context indicating the action is being done on behalf of an admin user or system that has "god" mode. Ideally you'd still tie those users or systems to roles and populate their permissions accordingly. – Jordan May 28 '19 at 20:47
  • I'm not saying I know this is right or that I've done this before and I realize it challenges conventional wisdom, but I've been thinking about this a good deal and the biggest issue I've found with this approach is in cases where your authorization checks have to call to an external service (perhaps the permissions are too large to populate in the context which might be an issue if you're doing something like cross-account actions). – Jordan May 28 '19 at 21:00
  • 2
    Udemy is an example of resource based authorization where roles would not suffice since student only have access to lectures if they have paid for it, and the has paid attribute is dynamic. – Tez Jul 08 '21 at 00:45
7

It's sometimes difficult to distinguish between real access control rules and domain invariants borderlining on access control.

Especially, rules that depend on data only available far into the course of a particular piece of domain logic might not easily be extractible out of the domain. Usually, Access Control is called before or after a domain operation is performed but not during.

Vaughn Vernon's assert (forum.IsModeratedBy(moderator)) example probably should have been outside the Domain, but it is not always feasible.

I would need to pass not just the userId, but the identifier of the protected resource (the id of the post, forum, etc.) to the security context, which probably should not care about these things (is it correct?)

If there's a Security BC and you want it to handle that logic, it doesn't have to know what a Forum is in detail but :

  • It could just have knowledge of concepts like "moderated by" and grant or deny access rights accordingly.
  • You could have adapter logic that subscribes to Core Domain events and translates them to simple (resource, authorizedUsers) key value pairs for the Security BC to store and use.
guillaume31
  • 8,358
  • 22
  • 33
  • As both answers are useful and more or less are pointing to the same direction I have upvoted them both. I accepted this one, as @guillaume31 has more or less answered my question about Vernon's implementation and I'll continue with my implementation based on his hint about using resources in the Security BC. – LittlePilgrim Mar 15 '18 at 12:07
  • I have to say I think this in the complete opposite of my answer. – Ewan Mar 15 '18 at 12:14
  • 1
    Maybe I'm too confused by now, but my interpretation was (for both answers): 1. Keep security concerns out of the domain and use the security BC as a service 2. Call the service before invoking any domain objects 3. The service will do the mapping from users/acls to moderators, authors, etc. `moderator = securityService.GetModerator(userId, forumId)` 4. Domain logic will be implemented in these objects like in moderator.EditPost() 5. Methods like EditPost will know nothing about security concepts, there will be no additional checks there – LittlePilgrim Mar 15 '18 at 12:58
  • I'm still looking for answers/direction on this myself, but I've found that any authorization logic which relies on the current state of the object (such as if it's currently assigned to a moderator) is in fact business logic which belongs in your domain and further that if it is not in your domain you risk ending up in an invalid state if the model can be updated concurrently. For instance if you validate ownership using a policy and then go to update that object - in many domains that ownership is subject to change and the action may no longer be valid. – Jordan Nov 29 '19 at 19:43
  • Unless you have a very complex collaborative context you can probably afford to implement an optimistic concurrency model here using versioning, but if your checks aren't done within or at least against a particular aggregate instance then your checks may fail to be consistent with the actual state of the object by the time you persist your changes. – Jordan Nov 29 '19 at 19:45