Authorizing requests using auth_request [http/authorization/auth_request]

The auth_request module is used for client authorization based on the result of a subrequest. Using njs along with auth_request can allow additional logic to be used for authentication.

Step 1: Use the following commands to start your NGINX container with this lab’s files:

EXAMPLE='http/authorization/auth_request'
docker run --rm --name njs_example -e SECRET_KEY="foo" -v $(pwd)/conf/$EXAMPLE.conf:/etc/nginx/nginx.conf:ro -v $(pwd)/njs/:/etc/nginx/njs/:ro -p 80:80 -d nginx

Step 2: Now let’s use curl to test our NGINX server:

curl http://localhost/secure/B
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.19.0</center>
</body>
</html>

curl http://localhost/secure/B  -H Signature:fk9WRmw7Rl+NwVAA759+H2Uq
<html>
<head><title>401 Authorization Required</title></head>
<body>
<center><h1>401 Authorization Required</h1></center>
<hr><center>nginx/1.19.0</center>
</body>
</html>

curl http://localhost/secure/B  -H Signature:fk9WRmw7Rl+NwVAA759+H2UqxNs=
BACKEND:/secure/B

docker logs njs_example
172.17.0.1 - - [03/Aug/2020:18:22:30 +0000] "GET /secure/B HTTP/1.1" 401 179 "-" "curl/7.58.0"
2020/08/03 18:22:47 [error] 28#28: *3 js: No signature
172.17.0.1 - - [03/Aug/2020:18:22:47 +0000] "GET /secure/B HTTP/1.1" 401 179 "-" "curl/7.58.0"
2020/08/03 18:22:54 [error] 28#28: *4 js: Invalid signature: fk9WRmw7Rl+NwVAA759+H2Uq

172.17.0.1 - - [03/Aug/2020:18:22:54 +0000] "GET /secure/B HTTP/1.1" 401 179 "-" "curl/7.58.0"
127.0.0.1 - - [03/Aug/2020:18:23:00 +0000] "GET /secure/B HTTP/1.0" 200 18 "-" "curl/7.58.0"
172.17.0.1 - - [03/Aug/2020:18:23:00 +0000] "GET /secure/B HTTP/1.1" 200 18 "-" "curl/7.58.0"

docker stop njs_example

Code Snippets

This config uses auth_request to make a request to an “authentication server” before proxying to the upstream server. In this case, the “auth server” is an internal location that calls our njs code.

nginx.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  ...

  env SECRET_KEY;

  http {
        js_path "/etc/nginx/njs/";

        js_import main from http/authorization/auth_request.js;

        upstream backend {
            server 127.0.0.1:8081;
        }

        server {
            listen 80;

            location /secure/ {
                auth_request /validate;

                proxy_pass http://backend;
            }

            location /validate {
                internal;
                js_content main.authorize;
            }
        }

        server {
            listen 127.0.0.1:8081;
            return 200 "BACKEND:$uri\n";
        }
  }

The njs code will look for a “Signature” header and compare its contents with a digital signature generated by the crypto library of njs. It also makes sure only GET requests are allowed.

auth_request.js
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  function authorize(r) {
      var signature = r.headersIn.Signature;

      if (!signature) {
          r.error("No signature");
          r.return(401);
          return;
      }

      if (r.method != 'GET') {
          r.error(`Unsupported method: ${r.method}`);
          r.return(401);
          return;
      }

      var args = r.variables.args;

      var h = require('crypto').createHmac('sha1', process.env.SECRET_KEY);

      h.update(r.uri).update(args ? args : "");

      var req_sig = h.digest("base64");

      if (req_sig != signature) {
          r.error(`Invalid signature: ${req_sig}\n`);
          r.return(401);
          return;
      }

      r.return(200);
  }

  export default {authorize}