0

I'm looking for a way to avoid centralized management of authorization rules. I'd like every microservice to be responsible for the authorization logic of its actions, but I'm having some trouble implementing that. Let's use an online marketplace application as an example (like eBay). A user in this application can manage multiple online stores, and every store may be managed by multiple users. The microservice we are working on is responsible for product listings management. There is an authorization rule that says that only a manager of a store can add products to it. In order to handle the authorization check, the microservice must hold the list of stores the user manages. Let's review three ways we can achieve that:

  • Call the microservice that holds the information (the one that is responsible for store management) using synchronous request/response communication. This creates coupling between them, both at the conceptual level (but it's fine in this case because they are both aware of the concept of stores anyway) and more importantly, at a technical level. If the Stores Management microservice goes down, the Product Management service won't function. This means the Product Management microservice is no longer operationally independent. Responses can be cached to minimize latency, but this won't solve the availability problem completely.
  • Hold a copy of the data in a local database, that is eventually consistent with the external microservice using Integration Events. The problem with this approach is that the Product Microservice will hold too much responsibility regarding store management, and this gets even worse when other microservices need to implement a similar authorization check - they'll all have to subscribe to creation and deletion events, as well as events indicating changes in the association of users and stores, published by the Store Management microservice.
  • Embed the list of stores in the user's access token (using JWT claims for example), but this feels wrong for a couple of reasons. First, associating a user to a store will require the generation of a new access token for the user (and so will other related operations). Second, I think of claims as a way to store parts of the identity of a user. Starting to save application data in them seems like a slippery slope.

I know this example may not be perfect so I'll try to generalize the problem: How can individual microservices enforce tenant isolation in an application where data for all tenants is stored in the same databases, and users may belong to multiple tenants?

2 Answers 2

1

Microservices by definition aren't operationally independent. That is part of their tradeoffs, it is far easier to build and deploy 10 different small services, but you may need to ensure 5 of those services are always available for any application to work. That is why virtual machines and containers exist in part, they make recovering from a down state faster so it's possible to never actually notice being down. Managing the dependency graph is part of the cost of microservices, the import part is just making sure everything isn't fully dependent on each other and everything fails if anything fails.

Having a list of who the manager of each store is stored somewhere the service can access isn't a terrible solution, and just having claims manage who managers are, this is what products like Kafka or RabbitMQ are made to handle. In cases where the stores a manager is responsible for changes frequently this is probably a better solution. In this case the product service isn't taking any store management responsibility just by storing events published by a different service, unless you start serving that information outside the product service.

Storing the list of stores someone is a manager of inside the token isn't a bad practice either. It is identity information that user X is manager of stores 1,3, and 22. Generating new tokens isn't that big of a deal in the general case. This is a good solution if a person is a manager of relatively few stores that don't change too often. If managers have hundreds or thousands of stores it may be better to manage that outside the token simply to keep the token smaller. similarly if management of stores changes frequently this probably isn't the best solution.

0

You can combine benefits of 1&2 by introducing generic and flexible replication infrastructure. In a nutshell, make an universal way for micro-services to exchange information, with ability to choose between cache or a live request.

Here is a more concrete example.

First, the Replication Broker: it's role is mediate wiring of replication streams. Replication Broker may be dedicated micro-service or just a component of a service coordinator (if you have one). The point is that micro-services with Replication Components should be able to find each other.

Next, replication streams are owned by Replication Component. Each data-owning or data-requesting micro-service should create an instance of Replication Component, then wire it to service's cache and persistence layer(s) on one side (own CRUD notifications go in, other's CRUD notifications pop out) and other micro-services' replication components on other side (ask the Replication Broker who is where).

Replication stream is a p2p connection, that allows data-owners and data-consumers to exchange CRUD notifications and even to do full state transfers (in case some micro-service just started — no matter on which side of a pipe).

For performance, you want replication streams to be configurable to work in 2 modes: push and pull. Ideally, mode set per model (table). In push mode, data-owner will send CRUD on every update. In pull mode, data owner would wait for explicit request. Mode choice is based on data-owner's relative update frequency (pull if faster, push if slower).

From POV of a micro-service's business layer, all remote data is requested asynchronously. It's up to Replication Component to handle these responses: route them to the owner or grab from the cache. Because replication is concentrated in one small place, coupling between micro-services should not be a big deal.

Wrt availability — you'll have to spin multiple instances of same micro-service, on different servers. This is whole can of worms that may require sharding and whatnot. But in some cases you may keep spare dormant instance, that may quickly replace fallen comrade — I found this to be useful when dealing with faulty service.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.