Question

We plan to refactor our company system into a micro-service based system. This micro-services will be used by our own internal company applications and by 3rd party partners if needed. One for booking, one for products etc.

We are unsure how to handle roles and scopes. The idea is to create 3 basic user roles such as Admins, Agents and End-Users and let the consumer apps to fine-tune scopes if needed.

  • Admins can create, update, read and delete all resources by default (for their company).
  • Agents can create, update and read data for their company.
  • End-Users can create, update, delete and read data, but cannot access same endpoints as agents or admins. They will also be able to create or modify data, just not on a same level as agents or admins. For example, end users can update or read their account info, same as agent will be able to do it for them, but they can't see or update admin notes.

Let's say that agents by default can create, read and update each resource for their company and that is their maximum scope which can be requested for their token/session, but developers of client (API consumer) application have decided that one of their agents can read and create only certain resources.

Is it a better practice to handle this in our internal security, and let them write that data in our database, or let clients handle that internally by requesting a token with lesser scope, and let them write which agent will have which scope in their database? This way we would have to track only token scopes.

The downside of this, is that our team would also need to create fine-tuned access mechanisms in our internal applications.

With this way of thinking, micro-services and their authorization system should not be bothered with clients needs, because they are only consumers and not part of the system (even though some of those consumers are our own internal apps)?

Is this delegation a good approach?

Was it helpful?

Solution

Authentication and authorization are always good topics

I will try to explain to you how we deal with authorizations in the current multi-tenant service that I am working. The authentication and authorization are token based, using the JSON Web Token open standard. The service exposes a REST API that any kind of client (web, mobile, and desktop applications) can access. When a user is successfully authenticated the service provides an access token that must be sent on each request to the server.

So let me introduce some concepts we use based on how we perceive and treat data on the server application.

Resource: It is any unit or group of data that a client can access through the service. To all the resources that we want to be controlled we assign a single name. For instance, having the next endpoint rules we can name them as follow:

product

/products
/products/:id

payment

/payments/
/payments/:id

order

/orders
/orders/:id
/orders/:id/products
/orders/:id/products/:id

So let's say that so far we have three resources in our service; product, payment and order.

Action: It is an operation that can be performed on a resource, like, read, create, update, delete, etc. It is not necessary to be just the classic CRUD operations, you can have an action named follow, for instance, if you want to expose a service that propagates some kind of information using WebSockets.

Ability: The ability to perform an action on a resource. For instance; read products, create products, etc. It is basically just a resource/action pair. But you can add a name and description to it too.

Role: A set of abilities that a user can own. For example, a role Cashier could have the abilities "read payment", "create payment" or a role Seller can have the abilities "read product", "read order", "update order", "delete order".

Finally, a user can have various roles assigned to him.


Explanation

As I said before, we use JSON Web Token and the abilities that a user possesses are declared in the payload of the token. So, suppose we have a user with the roles of cashier and seller at the same time, for a small retail store. The payload will look like this:

{
    "scopes": {
        "payment": ["read", "create"],
        "order": ["read", "create", "update", "delete"]
    }
}

As you can see in the scopes claim we don't specify the name of the roles (cashier, seller), instead, just the resources and the actions that are implicated are specified. When a client sends a request to an endpoint, the service should check if the access token contains the resource and action required. For example, a GET request to the endpoint /payments/88 will be successful, but a DELETE request to the same endpoint must fail.


  • How to group and name the resources and how to define and name the actions and abilities will be a decision made by the developers.

  • What are the roles and what abilities will have those roles, are decisions made by the customers.


Of course, you must add extra properties to the payload in order to identify the user and the customer (tenant) that issued the token.

{
    "scopes": {
        ...
    },
    "tenant": "acme",
    "user":"coyote"
}

With this method, you can fine-tune the access of any user account to your service. And the most important, you don't have to create various predefined and static roles, like Admin, Agents, and End-Users as you point out in your question. A Super User will be a user that owns a role with all the resources and actions of the service assigned to it.

Now, What if there are 100 resources and we want a role that gives access to all or almost all of them?. Our token payload would be huge. That is solved by nesting the resources and just adding the parent resource in the access token scope.


Authorization is a complicated topic that must be addressed depending on the needs of each application.

OTHER TIPS

I think that no matter what, you'll want your services to accept an auth token that is provided by a authentication service that you write to validate users. This is the most straightforward/secure way to prevent misuse of your microservices. Also, in general, if you want a client to have a good experience, you should implement the critical features yourself and test thoroughly to insure that the features you offer are well implemented.

Since all callers need to provide your microservices evidence that they've been authenticated, you may as well tie permissions to that authentication. If you provide the ability to tie a user to an arbitrary access group (or groups if you want to get fancy, though permissions add versus subtract is harder to deal with here.), there will be fewer questions coming from your clients about why user x was able to perform an undesired operation. In any case someone has to do access list checking for each service, so it may as well be you. It's something that would very easily coded at the beginning of all the services (if ( !TokenAuthorized(this.ServiceName, this.token) { Raise error }) That you may as well do it and keep track of the user groups yourself. It's true that you'll have to have a permissions group manager, and work it into the user management UI (use existing/create new group for user permissions) Definitely list the users tied to a group when you edit the definition, to avoid confusion. But, it's not a tough job. Just have metadata for all of the services, and tie looking up the mapping between group and service into the auth token handling.

Ok, so there are quite a few details, but each of your clients that want this functionality would have to code this in any case, and if you support the three level user permissions, you may as well just extend it to be per user access groups. Probably a logical intersection between base group permissions and user specific permissions would be the right aggregation, but if you want to be able to add and take away base permissions, of the Admin, Agent, End-user base permissions, you would have to do the ususal tri-state flag in the permissions groups: Add Permission, Deny Permission, Default Permission and combine permissions appropriately.

(As a note, this should all happen over something like SSL or even two-way SSL if you are concerned about the security of both ends of the conversation. If you "leak" these tokens to an attacker, he's in as if he'd cracked a password.)

My view is that you have two choices here.

  • If you just need to have configurable access to essentially the same application, then have the services check permissions and give your customers an interface which allows them to change the permissions given to each role. This allows most users to use the default role setup, which the 'problem' customers can tweak the roles or create new ones to suit their needs.

  • If your customers are developing their own applications, they should introduce their own intermediate api. Which connects to yours as admin, but checks the incoming request against their own custom auth requirements before calling your services

Security consideration

If I understand well your design, you intend to delegate some resource access control mechanisms to the client side, i.e. a consuming app reduces the items a user can see. Your assumpption is:

micro-services and their authorization system should not be bothered with clients needs, cause they are only consumers and not part of the system

I see here two serious problems for serious business:

  • What if some rogue user out there (for example in one of your partner's plant) reverse engineers the client app and finds out about the API, circumvent the restrictions that his company has put on the client and uses that information to harm your company ? Your company will claim damages, but the parnter company will argue that you didn't give the means to protect your data sufficiently well.
  • Usually, it's only question of time that sensitive data is misused (or audit will discover the risk) and your management will end up asking for more tightly control of that data.

This is why I'd advise to anticipate such events and take care of authorization requests. You are in an early reengineering phase and it will be far easier to take these into account in your architecture (even if you don't implement them all) than later on.

If you pursue on your current position, consult at least your information security officer.

How to implement it

You have the trick:

With this way, we would have to track only token scopes.

Ok, you intend to use some general tokens chosen by the client. Again a weakness in my eye, because some clients can be out of your control.

I don't know if you already use JWT or if you use some other techniques. But if you use JWT, you could have an identity token that carries the identity of the user (and even a second token that securely identifies the originating app, which could allow you to differentiate the level of trust between in-house clients and external clients).

As you intend to go for a microservice architecture, I'd like to suggest to amke the difference between the user management and authentication process (which should run as a dedicated service) and the access control (which is specific to each microservice, and should be handled locally by each of them). Of course some admin client should give a comprehensive overview across several services, for ease of use).

Here, there's a short answer too. You should implement all the core features you want to offer to your "clients" yourself. It seems problematic to have clients add such fundamental behavior as user permissions themselves, since you are already doing user authentication; if you leave it to the client to implement it, you may end up "supporting" multiple implementations of the same permissions code. Even though you don't "own it," there will be bugs in their code and you want your clients to have the functionality that they were expecting, within reason, so you support the resolution of the issues a client is having. Supporting multiple code bases is no fun.

Licensed under: CC-BY-SA with attribution
scroll top