One of the things engineering teams face when moving towards microservices architecture is how to implement authentication and authorisation in a way that is secure and scalable at the same time.

We were no exception and I'd like to take you on a journey of what that transition was like for us and how to learn to trust no one ;)

But before we jump in, let's begin with some basics.

Authentication

Authentication is the process or action of verifying the identity of a user or process.

That means basically telling who a user is by challenging them to validate credentials (for example, through passwords, answers to security questions, or facial recognition).

Authorisation

Authorisation is the process in which an application determines if the accessing user or service has the necessary permissions to access a resource or perform a given operation.

Authorisation is also a way of saying "you are permitted to do what you are trying to do".

Both usually come together and if you can't prove your identity, you won't be allowed into a resource. And even if you can prove your identity, but you're not authorised for that resource, you will still be denied access.

The problem

An obvious solution that first comes to mind is to create a dedicated 'Identity service' that serves as the source of truth for any login data.

It would handle the authentication problem by issuing short-lived JWT tokens in exchange for the correct login / password pair.

Identity service allows for one source of the truth for any login data

When it comes to authorisation, however, this approach doesn't scale, since each service would need to delegate trust to Identity service every time there's a request, in order to validate user permissions.

Zero trust

A better way would be to try to avoid trust delegation to any central authority in order to authorise a user.

This can be done by stuffing user permissions inside JWT token and verifying permissions locally without help from an external party. It is possible since JWT standard supports custom payloads that allows us to put any data in there when issuing or refreshing a token.

{
"sub": "1234567890",
"name": "John Doe",
"permissions": [
"accounts.manage",
"payments_methods.view",
"reports.view",
"user_management.admin"
]
}

Signature

There wouldn't have been the need for such an article if there wasn't one caveat, though :)

Header --> Payload --> Signature

JWT tokens are meant to be used in an insecure environment like public Internet. For this reason, you are going to want to securely sign the token with a private key, so that when the request reaches your app, you can verify the payload hasn't changed with a public key.

That approach simplifies configuration management, since you'll need to secure the private key once and distribute public keys safely across microservices.

This technique is possible thanks to Public-key cryptography, or asymmetric cryptography - a cryptographic system that uses pairs of keys: public keys, which may be disseminated widely; and private keys, which are known only to the owner.

Alice and Bob's user verification using private and public keys

This article doesn't provide answers to all possible questions, but you should now have a much better understanding of a zero trust approach in general.

To find out more about the topic I recommend checking out Microservice Authentication and Authorization by Nic Jackson of HashiCorp and Introduction to JSON Web Tokens.