3

We are currently working on a micro-service based system. The idea behind it is to split our monolithic system to small domains (micro-service per domain).

All services will be used by our internal applications, but we have plans to expose it to public.

One of the services would be identity management service, which would be responsible for issuing and validating tokens.

For shared references, we are using SharedKernel. One of libraries in shared kernel is a custom AuthorizationAttribute, which will be used by all micro-services. Basically what the attribute does, is forwarding Bearer token to authorization service, and authorization service responds with information such as token validity, user information and a set off allowed endpoints/resources for the given token.

If the current endpoint is not listed, 401 HTTP response is returned.

Now, I have 2 concerns:

  1. Are we on the right track and is this a valid approach? If not, what can be done to improve it?
  2. We are still having problems with UserTypes/Roles concept. We will probably have 3 types of users. Admins, Agents and End-Users and yet again, if some 3rd party company decides to use our API, roles which were created for our needs, might not be suitable for their needs. The problem is that not all agents will have set of same permissions. For an example we might have an agent who is responsible for accounting and he would have access only to accounting related micro-services, but we might also have an agent who is responsible only for booking. Should this require us to set a UserType for each agent type, fe. AccountingAgent, BookingAgent etc, and assign multiple UserTypes to user, or there is a simpler approach to solve this?
gnat
  • 21,442
  • 29
  • 112
  • 288
Robert
  • 545
  • 6
  • 16
  • 1
    Robert I voted down by mistake, sorry about that. I asked moderator to undelete and retracted my vote. Please feel free to re-delete if I misunderstood and the reason was not that unfortunate downvote – gnat Aug 18 '16 at 12:57
  • @gnat I was not sure is this a place to ask or StackOverflow, so i thought I missed :) – Robert Aug 18 '16 at 12:58

2 Answers2

6

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

  • Client -> Auth : please give me a token, here is my username and pass
  • Auth -> Client : here is a token I have signed it with my private key and it contains the claim "i am an Accounting Agent"

then

  • Client -> Resource : hi, please give me a list of accounts, here is my token
  • Resource -> Client : I have checked the signature on your token using the Auth services public key. so I know I can trust your claim that you are an Accounts Agent. Here is the list of accounts

then

  • Client -> Resource : Please delete this account, here is my token
  • Resource -> Client : sorry Account Agents are not allowed to delete accounts

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:

If(users.Role != "RoleX")
{
    return "Access Denied!";
}

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

Ewan
  • 70,664
  • 5
  • 76
  • 161
  • this is JWT approach? So in case agent was Accounts and Product agent, we would simply have all that information stashed inside signed token? – Robert Aug 18 '16 at 14:09
  • yes, you could have multiple claims – Ewan Aug 18 '16 at 14:17
  • We will provide a set of our core domain functionalities, but they will have to register in our system, so they can use basic auth to receive a token. We are working with Company X and we have decided that Company X can access booking and product services. They should be able to fine tune access for each user withn their company. If agent A requests token, he will receive read only token for products, but if agent B requests token, he will receive read and update token, cause they decided so. – Robert Aug 18 '16 at 15:25
  • 1
    "No, you are not using tokens correctly." I think this is a little too strong. While there are clearly benefits to using the tokens to eliminate centralization around security, there are also drawbacks. Namely, revocation is more difficult and authorization is less flexible. "At the end of the day a service has to have a set of Roles or Permissions that it knows about." This is isn't true. The centralized approach allows the service to simply ask if user X can do action Y. It need not know what role the user has or whether there are roles at all. – JimmyJames Aug 18 '16 at 16:53
  • @jimmy that approach is simply having a permission per action. The service knows about actions obvs – Ewan Aug 19 '16 at 06:58
  • @Ewan If i understood correctly, you are talking about asymmetric encryption here? Claims will be encrypted by using client's public key, and signature will be generated by issuers private key? – Robert Aug 19 '16 at 08:30
  • @robert you can use symmetric encryption if you trust your Resource services, but the idea is that the Resource can only check the signature. if its symmetric then the Resource can also create a token, which you dont really want it to be able to do – Ewan Aug 19 '16 at 13:05
  • So, the only drawback is that the token cannot be revoked – Robert Aug 19 '16 at 13:07
  • 1
    well, the token should have an expiry. so the drawback, if it is one, is that the token will be valid up to expiry mins after logout rather than race condition milliseconds after logout – Ewan Aug 19 '16 at 13:14
  • @Ewan What the initial question stated is that it's a permission per resource. That implies that you might be allowed to put on /account/foo but not on /account/bar. You could include all of the resources in the token but if the app can create new resources, then you would need new tokens to access them. That might not really be needed but the point is by making the resources agnostic to authorizations, you get maximum flexibility. If you build out roles, you have to determine up-front what authorities to group together. – JimmyJames Aug 19 '16 at 14:00
  • @Ewan The other issue is access or authority is removed it won't take effect until the token expires. If it were determined that an account was compromised, the only option would be to bring the whole thing offline. It's unclear how big a risk this is but if they were thinking of making the tokens good for a day, let's say, it could be problematic. There are other solutions to revocation such as having the nodes periodically check for lists of revoked tokens or using something like [stapling](https://en.wikipedia.org/wiki/OCSP_stapling) but it definitely adds complexity. – JimmyJames Aug 19 '16 at 14:06
  • Usually you would have the tokens expire after a 5? or so minutes. The idea is that this length of time is comparable to your response time to an auth problem and so doesn't add significantly to your risk. Also, consider the situation where you do have each call hit your auth service, but are forced to distribute that service due to the load it is under. that would also introduce a possible delay between hitting 'LOCK USER OUT' and the user being locked out as the change propagated across the servers – Ewan Aug 19 '16 at 16:03
  • @JimmyJames i interpreted 'Resources' in this case to refer to rest CRUD resources rather than AccountId=12. if so the claim shoudl include the property from which the decision of access is made. ie Role=AccountUser, CompanyId=abc – Ewan Aug 19 '16 at 16:06
  • @Ewan, i saw you wrote that authentification server will sign token with it's private key. Would you care to elaborate a bit? I thought that purpose of asymetric encryption is that the private key is used for decryption and public for encryption – Robert Aug 19 '16 at 16:44
  • 1
    yeah but you reverse it for signing – Ewan Aug 19 '16 at 18:31
  • @Ewan That's could definitely work for that requirement but the question specifically referred to a 3rd party whose requirements were not know. You are still locked into an approach. Of course, an external call is not required either. The authentication rules could be injected into the service configuration – JimmyJames Aug 19 '16 at 18:33
  • 1
    @Robert If the signature were made with the public key, 1. anyone could do it and 2. only the holder of the private key could check it but 3. it would be pointless because of 1. The point is that only one party can sign with the private key and anyone can verify that they signed it using the public key. – JimmyJames Aug 19 '16 at 18:42
  • @jimmy I believe I address that in my answer. Any way you cut it, the service has a fixed list of permissions/actions – Ewan Aug 19 '16 at 22:15
  • Guys, you have been very helpful. I saw pros and cons of each approach. I will brainstorm a bit more on this Topic. Jwt sounds like a way to go, but if we decide to centralise authorization, we will probably store sessions in Redis. Next topic which troubles me, can be found here http://programmers.stackexchange.com/questions/328784/authorization-and-authentication-system-for-microservices-and-consumers) – Robert Aug 19 '16 at 23:30
  • 1
    ..do not use sessions. Your services should be stateless. This is much worse than a central auth – Ewan Aug 20 '16 at 07:04
  • @ewan, by sessions i mean active tokens – Robert Aug 21 '16 at 17:28
1

Unless you have something else in place such as TLS to hide the token, realize that if the token is intercepted, anyone could use it to pretend to be that user for the length of it's validity. Personally, I would prefer something like a client certificate that doesn't require passing secrets and eliminate managing sessions from the application but there's nothing wrong with the token approach as long as you understand and account for the risks.

One advantage I see to having a separate service that responds with what the user is authorized for is that it can be replaced as needed. So if a third party wants to do something different, they change the behavior of that service. You can simply say "here's user X" what can he or she do? This completely decouples the authorization structure from the service implementations.

Using a separate service to manage authorizations does create some potential challenges. Mainly it could become a single point of failure and it will receive a request for every call to any service. You should be able to manage this with horizontal scaling throughout it's implementation. You could also incorporate caching to avoid excessive chattiness.

JimmyJames
  • 24,682
  • 2
  • 50
  • 92
  • Yes, response from authorization server provides basic user info such as ID and list of endpoints which are valid for this user. for an example `{endpoint: "api/accounting/bills" , scope: "CRU"}`, which suggests that user has access to "api/accounting/bills" endpoint and can use GET, POST and PUT verbs, but not delete, since user lacks "D" (lol) from the CRUD scope. – Robert Aug 18 '16 at 13:54
  • @Robert It's not really related to your question but I think it's worth pointing out that the CRUD operations don't align completely with the HTTP operations. For example, the PUT operation is generally able to create data as well as update it. – JimmyJames Aug 18 '16 at 21:18
  • I am aware of that, but if we go this approach, all our microservices will honor this principle. It's something we picked up by studying zendesk's api. We have to deal with scopes somehow. – Robert Aug 19 '16 at 09:10