I'm having a problem choosing the right approach to sharing some user related data throughout my microservices-based application.
Imagine the following scenario:
Users
microservice handles creation of users, but also management of the hierarchy of those users. It has the information regarding which users have Manager
role and which User
role, but also which Users
are subordinates of which Managers
.
There's also a Books
microservice which allows for creation and management of Book
and related entities. Users can create and manage their own Book
, however, their managers should be able to update their Book
, too. The authorization of Update
endpoint of BooksController
should check if the User
trying to do the update is the owner, or in the case when he's not, does he have a Manager
role and also if the owner of the Book
in question is his direct subordinate. This information is only available in the Users
microservice.
I'm considering following solutions to this problem:
A request/response pattern implementation to get the subordinates of the
Manager
in question - Feels like a very bad option for this, as it's essentially creating a tight coupling between the services and also creating a single point of failure for both services.Sharing the
Users
database withBooks
microservice (read-only) - another case of tight coupling, however, with no dependency of theUsers
service to be alive to retrieve the information.Merging the microservices together - Maybe the line was drawn in a wrong place and those two microservices should become one. However, looking at the
Users
service, it feels like this sort of scenario can reappear for other microservices that will be introduced to the application. Solving it this way sets a precedence for just merging it all back together into a monolithic application.Adding the data regarding users hierarchy to the access token as a custom claim and using that data to do Authorize in the
Books
service - I think it could work. My worry is that I'd be misusing custom claims for passing data that isn't really 'part' of the user.Other?
3 Answers 3
I'd go for option 5: Event based microservice architecture.
When a user is created, the user service emits a UserCreated event. If the created user is a manager, the event contains a list of UserIds of subordinates.
The Book service is subscribed to this event and stores the Id, list of subordinate UserIds and perhaps name or other details it's interested in, in its own data store.
This seperation leads to a loosely coupled yet robust system without the need for querying other services.
-
I'm not familiar with event based architectures, wouldn't that lead to repeated data and repeated business logic regarding manager/subordinate roles? The Book service would need to follow and interpret all events emitted by the User service which creates pretty strong coupling in case you implement new transactions within the User service which emit new types of events. And every other dependent of the User service would need to duplicate this code and data, too.Hans-Martin Mosner– Hans-Martin Mosner2020年07月13日 17:28:31 +00:00Commented Jul 13, 2020 at 17:28
-
1Repeated data: yes. Repeated business logic: no. The user service contains the business logic about managing users, the book service contains the business logic about books. Other services, for example a service that sends happy birthday mails to users, is also subscribed to the events, but only stores users names, emails and birth dates. No other system has to know this birthday system even exists. And yes, the book service is coupled to the user service, but loosely via the message bus. Replacing the user service is possible without changing the book service code.Rik D– Rik D2020年07月13日 17:38:11 +00:00Commented Jul 13, 2020 at 17:38
-
1This could be the answer. Personally, I think data replication is not a bad thing to do in microservice-based applications if done carefully. I'll throw this as another option in the next discussion with my team and see what happens.Slowacki– Slowacki2020年07月14日 16:35:07 +00:00Commented Jul 14, 2020 at 16:35
The authorization logic you describe is a business rule and one constant about business rules is that they are always subject to change. One such change could be that managers must be able to assign a "stand-in" who gets the authority to edit Books
of the manager's subordinates during a particular time frame or until it is revoked.
You don't want to have that kind of stuff in a claim in a token, because it would mean issuing a new token every time someone becomes a "stand-in" or stops being one. For these kinds of authorization business rules, I see only two plausible solutions
The
User
service has knowledge of all authorization rules for each possible claim and theBook
service asks theUser
service if the current request is authorized.The
Book
service knows the authorization rules and asks theUser
service for the required information.
In either case, the User
service becomes a central player in the authorization checks, so measures should be taken to ensure the User
service has a higher availability than each of the other services, for example by running multiple redundant instances of it that synchronize their databases.
-
Providing that the manager of a group of users would only change infrequently, wouldn't it be ok to invalidate the access token then? Regarding 1. it could be just something specific to me, but I thought that keeping authorization of one service outside of it is rather bad practice? As for 2., this basically means using the option 1. from my question, right? And what do you think about data replication like mentioned by Rik D here: softwareengineering.stackexchange.com/a/412676/229203Slowacki– Slowacki2020年07月14日 16:27:18 +00:00Commented Jul 14, 2020 at 16:27
I would not say I am an expert in micro-services, so here are some thoughts:
- It seems like you have identified a shared dependency (i.e. interrogating identities) between the 'manage users' feature and other internal services.
- Is there enough business logic around creating vs interrogating identities, that these services need to be split?
- For example Manage Users service has business logic around what and when users can be created, by whom, perhaps having a DB that is an audit trail. While the other Current User service is the most up to date view of users but has no edit functionality. This may also be a better segregation of interfaces.
- Would it make sense to validate the 'rights' of the requester at the first gateway/API level so that the 'not authorised' response is generated before the request hits the books micro-service?
- This would avoid having to add rights-checking to all of your services.
- Would you be happy with the Books service assuming that any request that arrived had already been checked for authority?
Compromise - if you are having 'rights checking' in various service, create a service to meet this need, which may itself depend on the Users service. That way, if you decide to unpack these responsibilities later, you have captured the common dependency in a single service.
-
Regarding justifying the split, I'm not sure. However, I've worked with a larger microservice based solution before and similar problems arose regarding handling of some users data that was required in other services. In the end most of the things that was needed, we packed into custom claims. It worked just fine, but I'm not sure if it's the best solution.Slowacki– Slowacki2020年07月13日 11:23:40 +00:00Commented Jul 13, 2020 at 11:23
-
As for 2., I'm a bit opposed to authorizing at the higher level. It goes against what I'm usually trying to follow: 'Authenticate globally, authorize locally', as described for example here: blog.andyet.com/2015/05/12/micro-services-user-info-and-authSlowacki– Slowacki2020年07月13日 11:25:08 +00:00Commented Jul 13, 2020 at 11:25