6
votes

I try to implement Oauth2/OpenId Connect into microservices architecture based on Java/Spring Cloud. My current problem is about tokens propagation between microservices or through a message broker like RabbitMQ.

Very few topics talk about this. I only found this Stackoverflow thread but I don't like proposed answers.

Here are the different cases :

My microservice A receives a request initiated by the end user going through the API gateway and carrying a valid access token (JWT with scopes/claims corresponding to the final user : username, id, email, permissions, etc.). There's no problem with this case. The microservice has all informations to process the request.

1st problem : What happens if microservice A needs to call microservice B ?

  • 1st solution : microservice A sends the access token to the microservice B

==> What happens if the token expires before arriving at microservice B ?

  • 2nd solution : use "client credentials grant" proposed by OAuth (aka service account). It means microservice A request a new access token with its own credentials and use it to call microservice B.

==> With this solution, all data related to the user (username, id, permissions, etc.) are lost.

For example, the called method in microservice B needs the user id to work. The user id value can be set as query string. If the method is called with the user access token, the microservice B can validate that the user id value in query string is equal to the user id value in the JWT token. If the method is called with the service access token, the microservice B can't validate the query string value and needs to trust the microservice A.

For this cas, I heard about the "token-exchange" draft from OAuth 2. The idea is very interesting. It allows microservice A to convert the user access token into another access token with less permissions but forged for microservice A. Unfortunately, this mecanism is still in draft and not implemented in a lot of products.

2nd problem : What happens if microservice A pushs a message to RabbitMQ and microservice B receives this message ?

  • 1st solution : Authentication and authorization are managed by RabbitMQ (vhost, account, etc.)

==> Once again, all user related data are lost. Moreover, we have 2 repositories to manage authentication and authorization

  • 2nd solution : Like the first problem, use "client credentials grant"

What do you think about it ? Is there another better solution ?

Thanks in advance.

1
Thanks for links but both are talking about a basic use case with no communication between microservices (via orchestration or choregraphy)styix
Have you gained any knowledge concerning this problem?officer

1 Answers

0
votes

It's quite straightforward. There are always two use cases that we will refer to as end-user and app2app. Always gotta handle both.

Consider a simplified architecture:

         Internet                
User ----------------> Service A +-------> Service B
      HTTP request               |
                                 +-------> Service C

Assume the user is authenticated by a token. JWT or anything else, doesn't matter.

The service A verifies the user token and performs the action. It has to call service B and C so it makes the appropriate requests and has to include the user token in these requests.

There can be a bit of transformations to do. Maybe A reads the user token from a cookie but B reads the token from the Authorization: Bearer xxx header (a common way in HTTP API to accept a JWT token). Maybe C is not HTTP-based but GRPC (or whatever developers use nowadays?), so the token has to be forwarded over that protocol, no idea what's the general practice there to pass extra information.

It's fairly straightforward and it works really well for all services dealing with end-users, as long as the protocol can multiplex messages/requests with their context. HTTP is an ideal example because each request is fully independent, a web server can process various stuff per path and argument and cookie and more.

It's also an extremely secure model because actions have to be initiated by the user. Wants to delete a user account? The request can be fully restricted to the user, there can't just be an employee/intern/hacker calling https://internal.corp/users/delete/user123456 or https://internal.corp/transactions/views/user123456. Actually, customer support for example may need to access these information so there has to be some limited access besides being the user.

Consider a real-word architecture:

         Internet                
User ----------------> Service A +-------> Service B --------> SQL Database
      HTTP request               |
                                 +-------> Service C --------> Kafka Queue
                                                                    |
                                                                    |
                                           Service X <--------------+
                                           Service Y <--------------+
                                           Service Z <--------------+

Passing JWT user tokens doesn't work with middleware that do not work on a end-user basis. Notably databases and queues.

A database does not handle access based on end-user (same issue with the queue). It usually requires either a dedicated username/password or a SSL certificate for access, neither of which have any meaning outside of that database instance. The access is full read/write or read-only per table with no possibility of fine permission (there is a concept of row-level permission but let's ignore it).

So service B and service C need to get functional accounts with write permission respectively to the SQL database and the Kafka Queue.

Services X,Y,Z need to read from the queue, they also each need a dedicated read-only access. They don't strictly need to have a JWT user token per message, they could trust that what's in the queue is intended, because whatever wrote to the queue in the first place must have had explicit permission to write to the queue.

It gets a bit tricky if service X needs to call yet another HTTP service that requires a user token. That's hard to do if the token wasn't forwarded. It would be possible to store the token in the queue along the message, but it's really ill advised. One does not want to save and persist tokens everywhere (the kafka queue writes messages and would basically become a highly-sensitive token database, erf). That's where the forwarding of user tokens shows its limits, at the boundaries of systems speaking different protocols with different access control models. One has to think carefully of how to architecture systems together to minimize this hassle. Use dedicated service accounts to interface specific systems that don't understand end-user tokens or don't have end-user tokens.