Hands-On: I wrote my auth logic with JS in Nginx; Will you?

Devops at Leapfrog Technology Inc.
Search for a command to run...

Devops at Leapfrog Technology Inc.
No comments yet. Be the first to comment.
Do you understand the difference between "looks good" and "is good?"

For more than 3 years I've been doing DevOps and involved myself in multiple projects ranging from simple static web applications deployed to some CDNs to multi-tenant systems with event-driven architectures. I've always found some ways to make my bo...
One of the problems that I face frequently when writing Infrastructure as a Code is how you structure it. To answer this question, I think there are a few indicators that decide how we want to structure. One resource will have less change compared t...

This is my first blog of multiple blogs on developing a pod troubleshooter turned into a compiler as a service. What do you call it CaaS? COMPaaS is cooler though, lol. I've been learning a lot of things in this journey, so I decided to share about i...

This may come as a surprise to you. How can someone use nginx configuration with Javascript? Fortunately, nginx supports a language called njs which is a strict subset of ECMA5. This can be used to further extend your routing logic, but don't be that happy because the feature you get with njs is very limited.
Lots of syntax doesn't work out of the box and I couldn't make it work with third-party npm modules such as jsonwebtoken etc. I couldn't even use esbuild to generate a single file build.
From the documentation of nginx:
njs is a subset of the JavaScript language that allows extending nginx functionality. njs is created in compliance with ECMAScript 5.1 (strict mode) with some ECMAScript 6 and later extensions. The compliance is still evolving.
I won't go with the installation process because you can get it easily in the documentation here. Also, you'd have to add the following repositories to have nginx packages available if you're also using Ubuntu.
# NGINX Repository
deb http://nginx.org/packages/mainline/ubuntu/ jammy nginx
deb-src http://nginx.org/packages/mainline/ubuntu/ jammy nginx
After, we've installed the njs module. Let's go ahead and write the js code for straight-up firing the hello world.
@machine:/etc/nginx sudo cat > index.js << EOF
> var hello = function (r) {
r.return(200, "Hello world!");
};
export default { hello: hello };
> EOF
root@machine:/etc/nginx
This is a very simple handler, which will return the string "Hello world!" with status code 200.
Let's plug it into the nginx.conf file.
user nginx;
worker_processes auto;
load_module modules/ngx_http_js_module.so;
http {
js_import index.js;
server {
location / {
js_content index.hello;
}
}
}
Now, when I send GET request to localhost with curl, I get the expected response.
root@machine:/etc/nginx# curl localhost
Hello world!
root@machine:/etc/nginx#
This is already very cool. But, we can do even more.
Let's create two simple services; one auth service and one for consuming the auth service.
Auth Service: Code

This is a fairly simple API. The login handler takes a POST request with username JSON key and generates a JWT token. The verify handler verifies the JWT token passed as a bearer token.
Let's test our auth service if it's working as intended.
regmicmahesh@machine:/etc/nginx$ curl -s localhost:8080/login -XPOST -d '{"username":"mahesh"}'
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTUyMjUzMjMsInVzZXJuYW1lIjoibWFoZXNoIn0.y7kOx9dScMwg0XjRj1AAa9xh3rZp0GzEzR0FC1f3x-w"}
regmicmahesh@machine:/etc/nginx$ curl -s localhost:8080/login -XPOST -d '{"username":"mahesh"}' | jq -r ".token"
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTUyMjUzMjYsInVzZXJuYW1lIjoibWFoZXNoIn0.XXkLCwspeYYy8kCONSSPve1T7AYX94GgzNB1rrdOygk
regmicmahesh@machine:/etc/nginx$ k^C
regmicmahesh@machine:/etc/nginx$ curl localhost:8080/verify -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTUyMjI5NTksInVzZXJuYW1lIjoibWFoZXNoIn0.GXnnmLuXRr8tu1Sx08DXcgs7N6au4huku-S62sR0JCU'
{"exp":1695222959,"username":"mahesh"}
User Service: Code

The user-service simply prints out the value of the header X-User-Id .
Now, we need to configure our nginx such that, every request first goes through the auth service, decodes the token if possible, and passes the username to user-service
First of all, let's configure the /verify .
location = /verify {
internal;
proxy_pass http://localhost:8080/verify;
}
This is okay enough. The auth-service is running on port 8080. This should pass the request to that endpoint whenever requested.
Now, we can simply use auth_request /verify . But, where's the fun in that? We want to set the username in the header dynamically.
Let's write a simple njs script to send a request to this endpoint.
function verify(originalR) {
function callback(r) {
originalR.return(r.status);
}
originalR.subrequest("/verify", { method: "GET", body: "" }, callback);
}
export default { verify };
The NJS script above simply sends the request to /verify but doesn't send the request body. It sends only the headers.
Let's add one more block in nginx to use this js function.
location = /auth {
internal;
js_content index.verify;
}
Now, we need to use this /auth endpoint as the authentication endpoint and everything should be good. We just wrapped our actual verification endpoint with a proxy. Why? Because now we can add our JS logic to set the header.
Let's define a variable to hold the value of the username.
server {
set $username "";
# other code
}
Now, we can set this variable from the NJS script.
function verify(originalR) {
function callback(r) {
if (r.status === 200) {
const body = JSON.parse(r.responseText);
originalR.variables.username = body.username;
originalR.return(200);
} else {
originalR.return(401);
}
}
originalR.subrequest("/verify", { method: "GET", body: "" }, callback);
}
export default { verify };
Now, we can send this variable as a header to the actual user service.
Adding it in the location, block it comes as follows:
location / {
auth_request /auth;
proxy_set_header "X-User-Id" $username;
proxy_pass http://localhost:8081;
}
Let's take it for a test-run. It works!
regmicmahesh@machine:/etc/nginx$ curl localhost -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTUyMjI5NTksInVzZXJuYW1lIjoibWFoZXNoIn0.GXnnmLuXRr8tu1Sx08DXcgs7N6au4huku-S62sR0JCU"
Request Received From: mahesh
regmicmahesh@machine:/etc/nginx$
Thanks for reading my blog. I don't think this is the best way, but just wanted to share what's possible with NJS scripting.