Microservices Architecture Example - Part 2

Intro

In Part 1 of this series we took a look at the high level user stories for a niche directory site that I'm putting together. In this article I'll be discussing how I mapped those stories to microservices, and tracking some of the architectural decisions that were made.

Authentication

The first thing all users need is to be able to sign up or login to the site. While some content is available to the public, features such as following an org would require a user to be identified.

There's a couple of ways of approaching this.

  • Roll our own authentication with a user database and login pages etc.
    • Rolling your own is considered bad security practice. But with the various frameworks available you're not necessarily building it yourself nowadays.
    • This approach requires designing, building and securing a user database, login pages, and oauth2 integration code to multiple providers (Facebook / Google etc).
  • Leverage existing authentication services e.g. Amazon Cognito or Auth0
    • These usually allow for signup / login through social sites
    • Usually provide their own login pages and flow
    • You don't need to care about the database and its security as the providers take care of it for you.
I opted to use Amazon Cognito's and its hosted login interface. This aligned with my plan to host everything on AWS. (Auth0 would be an equally good option as I understand it integrates well with AWS as well.)

Once i'd embarked down the Node.js Cognito implementation, I found the documentation and examples lacking for Node.js. AWS does provide the Amplify javascript library but this is geared towards front-end or mobile clients, and skips a back-channel validation step that is specific to servers doing authentication. There is also the AWS SDK but as far as I could tell, it does not allow use of the hosted login page provided by Cognito.

I wound up having to write my own code to retrieve and validate oauth tokens. This is not a secure practice and I'm hoping more standardized libraries become available soon.

So okay here we have our Authentication or 'Account service' - provided by Cognito, and below are the actions associated with it.

Authorization

Now users can sign up and login to the site. With Cognito we can define roles for our users site-wide e.g. 'Site Administrator' and assign this role to users ourselves through the Cognito management interface. The web application can use the roles to authorize users to perform actions on the site.

However there is a slight complication. We do want that this site be 'multi-tenant' - that is we have multiple organizations and users that can have differing access levels across different orgs.

My research into Cognito and Auth0 support for fine-grained authorization pointed to neither of them supporting this model; they both support just application level roles or some sort of hacky way to achieve object level authorization.

Options

  • Cognito provides roles and groups that can be configured to map to an organization. E.g. RoleAdmin01 for administrator of Org Id #1.
    • However there are limits to these, and it would result in a large number of roles depending on the number of objects being authorized. This is not very scalable or viable to manage.
    • If we were to add another class of objects later - say 'News' - this makes it even more complicated to manage.
  • Create a local mapping of users vs object id roles
    • This involves a new microservice and likely a user interface to manage this - more effort.
    • Some level of synchronization is required to ensure user authentication and authorization are both valid.
    • The good thing is this keeps the authorization closer to the other microservices and somewhat agnostic of the authentication provider.
    • Once this is built, the service and UI can be re-used for other projects.
    • Authorization can be easily extended across different objects e.g. orgs, events, news etc.

I went with defining a local repo of "users vs objects vs roles". E.g. User #01 has Admin access to Org #003. This keeps the authorization configuration separate from the authentication provider - i.e. no need to customize the authentication configuration based on the application level specifics of org ids and event ids.

Below is the Authorization micro-service and associated functionality. I called it the 'claims service' because it returns user claims to objects that are specific to the site.

Tokens

The problem now is that when we login, our site needs to check with two different sources to authenticate and authorize a user.

  • A JWT id token is provided by Cognito, verifying the identity of the user
  • A JWT claims token is provided by the claims service, providing the object-level access of the user

We need to pass these to each microservice for them to know the identity and access. Also each service needs to validate these tokens to ensure they're not expired nor tampered with.

Here is a sequence diagram of the login flow and a microservice call.

Now that we have our authentication and authorization figured out, lets move to the core microservices in Part 3 of this series.