Lua & Nginx for API Authorization
By: Nicola Luongo
Tags: Api Gateway, Lua, Docker, Nginx, Microservices

Lua and Nginx can be used in tandem to authorize API requests or reject them. This is a simple implementation that is particularly useful when we don’t want to over complicate things using external libraries. Here follows a simple example of how to achieve this using the lua-nginx-module .

To keep things simple we will use docker-compose to spin up a container that is running nginx with the lua-nginx-module already installed (the installation by hand is quite involved) and another container running a minimal, bare-bones http server. We will cover both cases: the failure case (forbidden or unauthorized) and the success case (where the request is allowed to go through).

Laying down the groundwork

Let’s start with a minimal docker-compose. The important point here is the network configuration (the network is named cluster) so that both containers can see each other. Note too that the proxy depends upon the microservice container early instantiation - this will make the nginx proxy_pass directive easier to configure using hostnames instead of IP addresses.

A very standard configuration. The microservice container will be spun up before the proxy container.

With that out of the way, let’s review the proxy container Dockerfile

This Dockerfile is the one that will be used to spin up a container running nginx. Thanks to fabiocicerchia for building an nginx image that already includes the lua-nginx-module

The only thing to note about the previous file is the nginx.conf which follows which has to end up in the correct folder /etc/nginx/nginx.conf

Nginx will be listening on port 80, on the local.lua host (Do not forget to add this host to your /etc/hosts ). There is nothing to configure when it comes to Lua because the container image has been configured to run Lua inside nginx.

If we removed line 16, no authorization logic would be run and the request will be proxied to the microservice container directly. Bonus: Because we defined a network in the docker-compose file, we can simply point the proxy_pass directive to the microservice host.

Now, we can finally take a look at our lua file which is where the magic happens.

The lua-nginx-module exposes the nginx global to the lua context which contains all of the request data. In this case, we try to grab a token from the Authorization header, and we return a 403 if we cannot find it (A 401 may be an even more appropriate choice).

Test driving the setup

Grab the code from the repo and clone it into your machine and run docker-compose build && docker-compose up

From a different terminal tab or window run curl local.lua -I. The request will not go through. The expected output is as follows:

HTTP/1.1 403 Forbidden

Server: nginx/1.19.6

Date: Thu, 24 Dec 2020 02:17:55 GMT

Content-Type: text/plain

Connection: keep-alive

Now, instead, make a request with the correct header and a token.

curl local.lua -H “Authorization: Bearer secret-token” -I

The output should be the following:

HTTP/1.1 200 OK

Server: nginx/1.19.6

Date: Thu, 24 Dec 2020 02:20:29 GMT

Content-Type: text/plain

Connection: keep-alive

Analysis

The access_by_lua_file directive can be included inside any location block and used to authorize or block requests accordingly. A block without the directive may be reserved to authenticate users and give them a token, which they would include in subsequent requests. Said block may point to a microservice or to a single service that exposes an authentication endpoint.

In our minimal example, we simply grab a token from the requests headers and accept it as it comes. In reality, we would either check this token against a persistence layer and attempt to match it against a user. Further, we would verify if said user has access to the resource he requested. Lua includes several modules that communicate with redis, mysql, etc.

This setup is not production ready and several steps will have to be taken for it to be considered secure. Listening through https would be the bare minimum. Taking care of not running containers as root would be another precaution to take.

This example is a simple stepping stone for those who are already using nginx and want to implement a simple authorization mechanism without bringing in extra tools with all its blows and whistles.

Aside from the lua-nginx-module, there is also a js-nginx-module (mentioned for completion). However, the js-nginx-module is not nearly as feature-rich as the lua-nginx-module.

Closing words

Nginx on its own is a very powerful tool including a myriad of features that make it one of the preferred web servers on the internet. Coupled with Lua, we can program very sophisticated logic and leverage nginx’s power directly.

Useful links

Github repository containing the code

Lua’s official site

Lua Nginx Module (lua-nginx-module) by OpenResty