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.
Best Answer
No, you are not using tokens correctly.
The idea is that you have an Auth service which issues tokens and Resource services which can validate the token and read the claims it contains. So the rather than forwarding the bearer token to the auth service each time the flow should be as follows
then
then
This flow prevents the Auth service becoming a performance bottleneck and leave the various microservices to decide if Claim X can do Operation Y
Roles
To address your second question about roles and permissions. At the end of the day a service has to have a set of Roles or Permissions that it knows about. eg you have to actually code something like:
Rather than have a whole list of different permissions, 'canEditAccounts' ,' canEditCustomers', 'canDeleteAccounts'... etc etc the modern approach is to instead have a shorter list of Roles 'AccountingAgent' which essentially act as a set of permissions.
If a third party consumes your services though they will have to use the roles that the service knows about. This means that they are stuck with the level of granularity that you provide in your Roles.
ie If your Accounting Agent does too little and your Accounting Manager does too much for your third parties needs you are stuck.
One way around this is to have a dynamic mapping between Roles and Permissions. Have your service know about permissions and query what permissions a role has.
You can then allow your third party consumers to create their own roles, selecting the permissions they want each to have