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

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](http://www.ecma-international.org/ecma-262/5.1/) (strict mode) with some [ECMAScript 6](http://www.ecma-international.org/ecma-262/6.0/) and later extensions. The compliance is still [evolving](https://nginx.org/en/docs/njs/compatibility.html).

I won't go with the installation process because you can get it easily in the documentation [here](https://nginx.org/en/docs/njs/install.html). Also, you'd have to add the following repositories to have nginx packages available if you're also using Ubuntu.

```bash
# NGINX Repository
deb http://nginx.org/packages/mainline/ubuntu/ jammy nginx
deb-src http://nginx.org/packages/mainline/ubuntu/ jammy nginx
```

### Running Hello World

After, we've installed the njs module. Let's go ahead and write the js code for straight-up firing the hello world.

```javascript

@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.

```apache
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.

```javascript
root@machine:/etc/nginx# curl localhost
Hello world!
root@machine:/etc/nginx#
```

This is already very cool. But, we can do even more.

### Implementing Dummy Auth and User Service

Let's create two simple services; one auth service and one for consuming the auth service.

Auth Service: [Code](https://pastebin.com/uZG7Bx1e)

![Auth Service](https://cdn.hashnode.com/res/hashnode/image/upload/v1694965133302/8fc536cb-07ec-4912-984e-00ae2f25fa08.png align="left")

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.

```bash
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](https://pastebin.com/vm04if6Y)

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1694965227895/a6ee4ee2-0de0-44d3-a80d-8d57100807ab.png align="left")

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`

### Writing Nginx Config

First of all, let's configure the `/verify` .

```nginx
 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.

```javascript
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.

```nginx
 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.

```nginx
server {
	set $username "";
# other code
}
```

Now, we can set this variable from the NJS script.

```javascript
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:

```nginx
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!

```bash
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.
