Authentication Middleware in Express Gateway using JWT

Photo by Jacob Aguilar-Friend on Unsplash

I was trying to create my first actual microservice program and very soon I faced an issue: “How many times I should do the authentication?”

In the first part, I describe the situation and in the second part, I show how should we configure the express gateway to perform the jwt authentication and pass the claims in the request to the service endpoints. Feel free to skip the first part and go straight to the second part. And if you only want the express-gateway jwt part, go to Adding the authenticated data to the request at the end of the article, there is a complete config which could be used.

Problem statement!

I was going to use jwt token authentication for users. I had 2 microservices, one for user authentication (sign up, sign in, sign out) and one for CURD operations on a model, we call them auth-service and curd-service. I started writing code, and developed the auth-service, as you know jwt uses a key to deal with the tokens (create them or verify them). Everything worked fine until I started to develop the curd-service. I needed to authenticate the user in the request before performing the desired curd-service operation, but how? 

When the request (let say GET

/books/:_id)

entered the system, it didn’t necessarily go through the auth-service, and it probably contains the jwt token in the header. If I wanted to verify and decrypt the token in the crud-service I needed the secure-key in the crud-service. It instantly raised the question of whether I should put the secure-key in the curd-service or not? What if there are many services which may be at the front line of a system, do all of them need to have access to the “secure“-key? I started to look for a solution, I wasn’t the first person who had more than one service in his system. I found very good articles and learned a lot.

Long story short, one of the solutions is to have an API gateway, so every request goes through it. Adding an API gateway to your architecture has many benefits, it can perform rate limiting, request logging, acting as a proxy, handing CORS and also authenticating the requests. Although like anything else in life, it comes with its drawbacks, it can become a bottleneck or a single point of failure in the whole system. Anyway, I decided to use an API gateway as seemed (and still does) like a very good solution. By using API gateway, we can perform the authentication at the API gateway and only put the secret-key at the API gateway (besides the auth-service itself).

I started looking for an API gateway and decided to go with Express-Gateway

Configuring Express Gateway for jwt Authentication

The documentation on the Express Gateway is pretty neat and you can find almost anything where it should be. Here we are going to configure the express gateway to add the decoded authentication data ( jwt claims) to the request before passing it to our other services.

As is mentioned in the express gateway docs, it supports different ways for handling the jwt token in the request. Here I use the default way which is having

Authorization

header with

Bearer

keyword.

1. Starting Point

To be on the same page, let’s say we have an auth-service which performs login and register under

/auth/register

and

/auth/login

on the host

auth:3003

. We also have a curd-service for books which all start with

/books

and they are served at

books:4004

.

We need the api gateway to handle different requests.

/auth/register

and

/auth/login

need to be logged, and passed to

auth-service

as these request are not authenticated and thats the reason they are talking to

auth-service

. But for requests matching

/books*

the gateway needs to make sure they are authenticated (contain a valid token) and also decrypt the token and add its content to the request before handing the request to

curd-service

.

2. Create a new express gateway

a. Install Express Gateway

npm install -g express-gateway

b. Create an Express Gateway

$ eg gateway create

c. Answer a few questions

 ➜ eg gateway create
 ? What is the name of your Express Gateway? my-gateway
 ? Where would you like to install your Express Gateway? my-gateway
 ? What type of Express Gateway do you want to create? (Use arrow keys)
 ❯ Getting Started with Express Gateway
   Basic (default pipeline with proxy)
d. For running it, go to the folder and run

npm start

3. Configure the gateway to log and proxy the requests

After creating a new express gateway it generates a pretty complete and runnable template for us which only needs to be configured. All of the configuration we need to do are at the

/config/gateway.config.yml

. It also supports hot-reloading, so you don’t need to restart the express gateway every time you change something in the config files.

I’m not going into details of how to configure express gateway because as I mentioned before the documentation is fine. I just mention the core concepts and also suggest that you read the docs. There are a few core concepts at express gateway, apiEndpoints represent the endpoint of incoming requests (the ones system’s users are sending,

/auth/register

,

/auth/login

and

/books*

in our example). serviceEndpoints represent where the requests should be redirected to (

auth:3003

,

books:4004

in our example). pipelines are actually telling the express gateway how to handle the requests. principles tell the express gateway which tools we are going to use in the pipelines section (logging, authentication and proxy in our example).

We first define the log and proxy for

/auth*
http:
  port: 9080
admin:
  port: 9876
  host: localhost
apiEndpoints:
  auth:
    path: '/auth*'
    methods: ['POST']
serviceEndpoints:
  auth:
    url: 'http://auth:3003'
policies:
  - log
  - proxy
pipelines:
  authPipeline:
    apiEndpoints:
      - auth
    policies:
      -
        proxy:
          action:
            serviceEndpoint: auth
      -
        log:
          action:
            message: 'auth ${req.method}'
Here we are telling the express gateway to listen to port 9080 for incoming requests, if they match the pattern

/auth*

and their HTTP method is POST, log them and redirect them to

http://auth:3003

. Please note there is no authentication happening here.

4. Authenticate and adding the authenticated data to the request

If the requests contain a token which is generated using a secret-key, express gateway can authenticate and decrypt the token (assuming we provide it the same key) and put the authenticated data in the

req.user

. We further need to add

req.user

to the request before passing it to the desired serviceEndpoint.

Please notice that you need to update the policies section too. And just adding the new serviceEndpoint, apiEndpoint and pipeline is not enough.

http:
  port: 9080
admin:
  port: 9876
  host: localhost
apiEndpoints:
  auth:
    path: '/auth*'
    methods: ['POST']
  books:
    path: '/books*'
serviceEndpoints:
  auth:
    url: 'http://auth:3003'
  books:
    url: 'http://books:4004'
policies:
  - log
  - proxy
  - jwt
  - request-transformer
pipelines:
  authPipeline:
    apiEndpoints:
      - auth
    policies:
      -
        proxy:
          action:
            serviceEndpoint: auth
      -
        log:
          action:
            message: 'auth ${req.method}'
  booksPipeline:
    apiEndpoints:
      - books
    policies:
      -
        jwt:
          action:
            secretOrPublicKey: 'the-secret-key'
            checkCredentialExistence: false
      -
        request-transformer:
          action:
            body:
              add:
                user: req.user
      -
        proxy:
          action:
            serviceEndpoint: books
      -
        log:
          action:
            message: 'books ${req.method}'
the new pipeline (booksPipeline) is performing the proxying and logging action in the same way authEndpoint performs it. The difference is that we are using two new policies.

jwt

and

request-transformer

.

JWT

jwt

is in charge of authenticating the jwt token in the header, by default it tries to locate it in the

Authorization

header but it’s possible to define other approaches using the

jwtExtractor

and

jwtExtractorField

properties. If express gateway doesn’t find a valid token it responds to the request with a 401 error, which is great as the request won’t enter the system and there is 1 less request

curd-service

needs to be worry about.

secretOrPublicKey

specifies the secret-key, we could also use the

secretOrPublicKeyFile

.

By setting the

checkCredentialExistence

as

false

we are telling the express gateway we want it in the Uncontrolled modality mode, you can read about this here. Briefly, it is saying that we create and manage the tokens in another service (express gateway has the option to create the tokens through its admin API with Controlled Modality)

request-transformer
This is actually the part which took me a few hours to figure out, as it wasn’t mentioned the jwt section of docs for express gateway. As the name suggests

request-transformer

transforms the request by adding or removing different values to its header or body. If you don’t add this to your pipeline, the decoded information from the token won’t be available to the serviceEndpoint in which express gateway redirects the requests.

5. What comes next?

Now, when the request enters the

curd-service

we are sure it’s authenticated and it contains the decoded information of the token (user id, role, ….) in the

req.user

. You would also have the jwt token unchanged, if you wish to remove it from the request for any reason (maybe security), you can do it by another

request-transformer

policy which can also remove stuff.

I hope this has been useful, I’d be glad to get any feedback.

read original article here