Lua & Nginx for API Authorization
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.