24

I have a design issue I am not sure of how to solve.

Let's say my main application consists of 6 modules:

  • client
  • gateway
  • auth-service
  • forum
  • gallery
  • messages

The client is supposed to communicate with the gateway-service only.

Should I have my gateway do the user-authentication (which ideally results in a JWT) and the other 3 productive-services (forum, gallery, messages) just verify that token and retrieve permissions and roles they manage themselves for that given user?

To perhaps illustrate my few troubles, I create a sequence diagram: this image shows the sequence diagram, which I am having trouble with

Click here for the original draw.io graphics if you prefer that.

I do not want to use any 3rd-party auth-services; I just want my auth-service (which is pretty much done) to register users and let them login. Or should I be managing permissions and roles in that service as well?

I tried to wrap my brain around this issue for months, but I simply cannot find a suitable structure so I can let the user register, login/logout and communicate with various productive services. I am currently using Java for the backend stuff, but the good thing about microservices is, that I do not have to use one programming language for them all.

Any help here is welcome!

P.s.: I read Microservice Authentication strategy and Zuul - Api Gateway Authentication, but both did not seem to apply in my case.

2
  • 4
    @sschrass that's not very helpful... do you think I would have posted the question in all its details and with the diagram included if I had not thought about it at least twice?
    – Igor
    Commented Aug 7, 2018 at 15:10
  • for your users it probably is.
    – sschrass
    Commented Aug 7, 2018 at 16:11

3 Answers 3

16

I have worked with the following setup, and it has worked out pretty well:

Login flow:

  1. Fresh browser makes a request for a specific resource
  2. Gateway service detects the absence of the jwt cookie and redirects to login form
  3. Login form talks to auth-service via gateway. Gateway allows non-jwt calls only to auth-service
  4. Auth service gives a login-form a newly created jwt cookie and redirects to original URL

Normal operation:

  1. Browser makes a request for a resource along with jwt cookie
  2. Gateway service intercepts the request and forwards jwt to auth-service for validation
  3. Auth service checks signature then timestamp then blacklist and returns a positive or negative result
  4. If positive, Gateway service forwards the request to respective backend service, otherwise redirects to login
  5. Backend service does not do jwt validation - it just trusts the gateway to send only valid requests.
  6. Backend service does check for roles/permissions/entitlements defined in jwt

Logout flow:

  1. Browser makes a request to the auth-service/logout
  2. Auth service puts the jwt in blacklist and redirects to login form

Now, this is a simple workflow we implemented without any (much) 3rd party help. At some point we did had to use session cookies but that is for other reasons. Note that the system is almost stateless except the blacklist at auth service. One does not simply log out with jwt! We had a REDIS to manage the blacklists. You can implement logout with session cookies at gateway or auth service.

Most of the backend services expected their own set of roles/privileges/entitlements in the jwt. The roles were granted to user by the auth service and were written in the granted jwt. If a new role was grated to a user, the user had to logoff/logon to reflect that privilege. If some privilege is removed, then the user had to force logged off - that is where REDIS played.

5
  • 1
    I thought about something along the lines of your answer. I guess by REDIS you mean redis.io so I suppose I will give this a try (I already did something, but I did not continue work on it since it's a private project).
    – Igor
    Commented Aug 10, 2018 at 22:32
  • 1
    I disagree with the point 4 of "Normal flow" : it is easy and fast for your services to validate the JWT. That is "in-depth security" or "zero-trust architecture". Somebody may try to forge a malicious JWT and pass it to your service to acquire more permissions. Commented Aug 27, 2022 at 8:12
  • True. The answer is 4 years old. In all this time my experiences and understanding has evolved. Thanks for suggesting a better alternative. Commented Aug 28, 2022 at 2:58
  • @inquisitive does that mean, that you would not simply validate the JWT (based on its 3rd part, the hash) and trust it? Of course every backend service would do this. Of course I could introduce Redis or something to check if the JWT has been blacklisted or something, but other than that? How would a user go about forging a malicious JWT if the JWT is validated in every backend service?
    – Igor
    Commented Dec 6, 2022 at 0:25
  • @Igor if the jwt is validated in every backend service, then the possibilities of someone forging a malicious JWT is greatly reduced. Commented Dec 8, 2022 at 14:27
9

It depends on how complex the permission setup is.

If your permissions are super simple, i.e, all services need authentication and have a single a role to work with, you can add at gateway api.

If you need more granular approach you need to do it at the module level. Typically in a micro-service architecture, each service would invoke another service, and having the authorization settings at module level avoids up front knowledge of required permissions needed at gateway api. And you can deploy each of these modules without much thought about other services, have granular permissions at method level etc.

0
1

Long time has passed since this question was asked, but I have the same problem and came across this, so leaving my thoughts.

A nice overview of possible options is laid out here: https://www.osohq.com/post/microservices-authorization-patterns (I'm not related to oso in any way, just the blogpost is nice)

In short, what is described there is the following:

  • Leave the data where it is (basically what you described in your question, each service doing its own authorization). Easier and faster to get it going, but not responding well to scale (adding more and more teams and services)
  • Central authorization. Have a new central authorization service, which will hold all possible policies and roles, and each service will ask the central authorization service for permissions. Something like Policy Server.

What some of the answers here and the other stackoverflow questions you linked discuss is authentication (identity), not authorization (permissions). Both of them preferably should not be mixed in the same service. See this & this for more info.

3
  • Thank you for your response. I do understand the difference between authentication and authorization and while I could have a service (whether on-premise or in the cloud) do the authentication (e.g. Keycloak, AWS Cognito or the likes), I really wanted to know whether I should have each individual service store the corresponding permissions on its own or not. In my example setup I had each service keep a list of permissions for each userId. Then after validating the token (and thereby the userId in particular), the service retrieved the corresponding permissions.
    – Igor
    Commented Mar 24, 2023 at 10:53
  • 1
    No problem :) Just curius, did you stay that way of each service handling its own permissions, or went with centralized one? How did it go?
    – Boris
    Commented Mar 27, 2023 at 7:27
  • 1
    It was a homelab-grade multi-microservice application (3 services + auth I believe), but the issue was keeping track of the tokens as I didn't merge it all into one JWT, which in turn caused issues in the frontend. Sadly I lacked the time to continue on with this project later on, but if I did not, I might have switched to a centralized solution, because that way black-/whitelisting tokens would've been possible, too.
    – Igor
    Commented Mar 27, 2023 at 15:19

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