Building an authentication micro-service with JWT standard

Marcelo Fonseca
5 min readFeb 28, 2019

--

Micro-services introduction

Micro-services are getting popular nowadays and every popular programming language have both a big framework for web development and a micro-framework for smaller apps. These light frameworks are a good pick for a micro-service architecture project. Micro-services architecture has some advantage for being highly maintainable, independently deployable and allow us to take out the best of each programming language for a specific service. e.g. python service for web scrape or AI, javascript service for crypto libraries or ruby for active record. With this in mind, you’re not tied up to one single programming language to build your entire Back-End.

Here’s a list of popular micro frameworks for their respective programming language:

Authentication and Authorization problem

With a micro-service architecture, the authentication logic can grow to be a lot more complex then a regular client+server applications as front-end and back-end. You wont have just one back-end API server for one client, but several backends to manage and you’ll find yourself with a lot of apps and routes to protect. For this, there are many ways to create an authentication/authorization logic for micro-services architecture as shown in this article. In here I’ll walk through creating a simple authentication and authorization service using the JSON Web Tokens(JWT) standard.

Project architecture communication

For the sake of simplicity, in this example I’ll use two back-end services. I’ll build an expressJS app for authentication and authorization service and a sinatra app as a backend for a blog service. So far with this example we will be having 2 Back-ends and 1 Front-end. With this architecture It’s possible to add a third backend app and so on.

This is how the communication between applications will work:

Front-end and Back-End communication

  1. User will signup and Sign-in in the ExpressJS app from a FrontEnd app.
  2. If authentication succeed, ExpressJS app returns JWT token to user.
  3. Front-end adds JWT token to the request header and access Sinatra app data.

Inter-service communication

This is a scenario where we have a communication between Back-ends. Just for the communication example below, lets suppose we have a third flask API Back-end which will scrape the web and update data in our sinatra blog app. Which would give us a total of 3 back-ends and 1 front-end.

  1. Flask app request JWT token from ExpressJS app.
  2. if succeed, ExpressJS app returns JWT token to Flask app.
  3. Flask app adds token to the request header and can access Sinatra app back-end routes.

Two things to notice from the examples above. Just like users, your back-ends will need credentials to authenticate and access other back-ends. But your Back-ends wont be using email and password credentials. This can be done by creating an API KEY as their credentials. e.g. Flask app send an API key to a sign-in route in the ExpressJS app. If the API key sent is fine I’ll authenticate the flask service and grant him a JWT access token.

Public/Private key token signing and verification

Using this architecture, all micro-services applications will use their own JWT library to authorize access and protect their API routes. For this we will be using JWT RSA256 strategy. My authentication service, ExpressJS, will hold both private and public keys. I’ll Sign tokens with a private key to authenticate users or apps and I’ll verify/decode tokens with the public key. Every other service I have will hold just the public key to verify.

To use RSA algorithm you’ll need to generate a private/public key pair. This can be done using SSL(click for more info) in your terminal as shown below. This will generate a key pair as .pem files:

Adding keys to .env

Add the keys as env variables so we don't have to store the .pem file in the project. As an .env file we must transform the key into a single line variable.

step 1: Run this in the terminal to move the generated key to the project .env file

echo "PRIVATE_KEY=\"`sed -E 's/$/\\\n/g' private.pem`\"" >> .env

Our env file should look something like this:

PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\n
dasdasdadasdasdasdasdasdasdasdadasdasdadasa\n
huehuauhhuauhahuauhauahuauhehuehuauheuhahue\n
-----END RSA PRIVATE KEY-----\n"

step 2: Edit the key to a single line variable. remove the lines and keep the \n

.env

For some apps extra steps may be necessary before accessing an .env file. e.g. installing an dotenv library.

Sign token

This is done in your User or API sign-in route. The example below is my User sign-in route in my ExpressJS authentication service. If the user credentials are correct I’ll access my private key rsa_2048_priv.pem and sign a new JWT token.

verify token

All services will need to verify incoming requests if they have a valid JWT token. This can be done by creating a middleware in your applications. The code must open your public key pem file and then verify and decode the JWT token. This is how they should look like in both ExpressJS and Sinatra services.

ExpressJS authentication and authorization middleware:

Sinatra authentication and authorization middleware:

Project database sync problem

Keeping the authentication and blog services apart may cause sync problems. It’s required an user model in both expressJS authentication service and sinatra blog service. The expressJS app requires user credential informations and sinatra app requires every other user information for that app (e.g. profile avatar, description, database relationships to Posts, Comments and other tables). There’s a few ways to solve this:

  1. Keep all user data in auth service. User table in the blog service will only hold the user expressJS service id(user_id) to reference and find the user from the authentication service.
  2. No user table in Blog service at all. Any model in the Blog service related to user will hold the expressJS user id.
  3. Keep only credential info in the auth service(e.g. email and password) and the rest in your blog app. To reference an user from auth service in the blog service, use an unique attribute. Could be the auth service user id like above or its email. For email you’ll need an user email attribute in both services.

Choose what suits better for your project. I’d go with option 3 to keep only the required data which makes sense for each service to have. This way i can reuse the authentication service for future projects with fewer changes and benefit from ruby Active Record and Pundit gems for User queries, authorization (e.g. admin pages) and models associations in my sinatra app. Just be careful to keep the users synchronized in both applications. When deleting or creating a user in ExpressJS app, synchronize or delete his data in Sinatra app.

--

--

Marcelo Fonseca

BS Computer Science — UnB. Full stack developer and Technology enthusiast. Interested in things related with education and entrepreneurship.