Why we use JWT instead of oAuth to secure microservice communication

Micromachine Police

Microservices are a great way to separate parts of a large stack. It's always good to break large projects up into smaller bits to chew and manage. It's the old adage of how do you eat an elephant. But we don’t want to eat an elephant, we want secure communications between our micro services.

Here at Authentise we have a microservice architecture. We like to keep things simple and small. To communicate between services we use REST. One challenge we had was to come up with a good solution on how to secure this REST interface. Another requirement was to keep it as stateless as possible, which is simple. We came across this nifty little gem called a Json Web Tokens or JWT. If you want to learn more about JWT’s go for it. There are plenty of documents on it. This article will focus on using them to secure RESTful communications between microservices using JWT’s.

A lot of service communications tend to be using OAuth. OAuth though, is complex and bloated. We like simple and small. Another drawback to OAuth is it does not play well with service to service communications without a web browser.

Let's imagine that we have two micro services. One is a service that manages user information. Since we like code names for our projects at Authentise we can name it Census. Census will contain a few REST endpoints. Census has an endpoint with the following for managing users. This call is for the “Census User”.

GET https://census.service/user/42/
{
  "uri": "https://census.service/user/42/",
  "name": "Census User",
  "email": "census.user@example.com"
}

The other service manages notifications such as sending email, SMS, or push notifications. Let’s name this one Herald. Lets keep it simple and only have emails as notifications. Here is the signature for Herald. This will request create a new notification.

POST https://herald.service/notification/
{
  "user": "https://census.service/user/42/",
  "type": "welcome-message"
}

The response will contain a URI in the location header for the newly created notification if it has been successful.

They are both simple and have their own jobs to do. However, they would need to communicate with each other to complete their jobs. To send a notification Herald will need to know where to send the welcome message notification. Which means it will need to communicate to Census to get the information it needs. Herald will need to run a GET request on the user's URI which is https://census.service/user/42/. That will return the information Herald needs to process the welcome email.

To secure this we will use JWT's. We decided to put the actual JWT inside the requests headers under JWT. JWT provides a few reserved claims that we will be using. The JWT that Herald creates will have the following payload:

{
  "iss": "https://herald.service/",
  "aud": "https://census.service/",
  "sub": "https://census.service/user/21/",
}

The iss is the issuer which is Herald. The aud is the audience which is going to be Census. Finally, sub will be the user uri for authenticating the user that is creating the notification in Herald. This way we can see if that user has access to see the Census User information. Exactly the same as if the user made an authenticated GET themselves.

Now that the JWT is generated Herald will sign the JWT using its own private key. Herald and Census have their own private/public key pair that can be used to validate their JWT's. This allows us to use asymmetric encryption to sign and validate the JWT.

Census will receive the request and will extract the JWT out of the JWT header of the request. Census will decode the JWT and validate that it knows who Herald is from the iss of the JWT. Census then find Heralds public key and validate the signature. If it's not valid it will return an appropriate http status code.

This assumes that Census and Herald already know who each other are and where the other's public key is to validate the signature.

One option is to use a URI for the public key in the iss and aud. In which case the other service can use that to get the public key and validate the signature. But, Census will still need to know it allows Herald to make such calls.

Since we have validated that the JWT is signed correctly and who it says they are Census can set the current authenticated user for the request, validate that the user has access to the requested information and return the user's details. Once Herald gets the valid response back it can continue processing and send out a welcome email using the user's email address and return a location header with the newly created notifications unique URI.

We can go even further and use this same mechanism to validate the creation of the notification. It gets a bit more complex because we want to make sure the payload of a 'POST' or similar request's payload does not get tampered with. In this case the JWT would look like this:

{
  "iss": "https://another.service/",
  "aud": "https://herald.service/",
  "sub": "https://census.service/user/21/",
  "bdy": "<md5 hash of the payload>"
}

This uses a custom JWT claim bdy which is just an MD5 hash of the payload sent to Herald to create the welcome email notification. This way the service can validate the MD5 hash against the payload and ensure that it has not been tampered with.

On another note, the JWT's are just signed not encrypted. Lets keep things simple and just use SSL. Which will protect and encrypt the request.

Of course this can get a little more complex but it's a basic example. We have created two micro services that work together to complete their tasks securely and communicate to each other. It's an elegant simple solution. Let us know what you think: tweet to @Authentise .