Authorizing requests based on request body content [http/authorization/request_body]
Sometimes authentication credentials are passed in the request body rather than through headers or the query string. Using njs, we have full access to the request body contents.
Step 1: Use the following commands to start your NGINX container with this lab’s files: Notice the SECRET_KEY environment variable
EXAMPLE='http/authorization/request_body'
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
No signature
curl http://localhost/secure/B?a=1 -H Signature:A
Invalid signature: YC5iL6aKDnv7XOjknEeDL+P58iw=
curl http://localhost/secure/B?a=1 -H Signature:YC5iL6aKDnv7XOjknEeDL+P58iw=
BACKEND:/secure/B
curl http://localhost/secure/B -d "a=1" -X POST -H Signature:YC5iL6aKDnv7XOjknEeDL+P58iw=
BACKEND:/secure/B
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 | ...
env SECRET_KEY;
http {
js_path "/etc/nginx/njs/";
js_import main from http/authorization/request_body.js;
upstream backend {
server 127.0.0.1:8081;
}
server {
listen 80;
location /secure/ {
js_content main.authorize;
}
location @app-backend {
proxy_pass http://backend;
}
}
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. If the request method is POST, we make sure the content-type is a form and then incorporate the request body (if present) into the digital signature.
request_body.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
34
35
36
37
38
39
40
41
42
43
44
45 | function authorize(r) {
var signature = r.headersIn.Signature;
if (!signature) {
r.return(401, "No signature\n");
return;
}
var h = require('crypto').createHmac('sha1', process.env.SECRET_KEY);
h.update(r.uri);
switch (r.method) {
case 'GET':
var args = r.variables.args;
h.update(args ? args : "");
break;
case 'POST':
var body = r.requestBody;
if (r.headersIn['Content-Type'] != 'application/x-www-form-urlencoded'
|| !body.length)
{
r.return(401, "Unsupported method\n");
}
h.update(body);
break;
default:
r.return(401, "Unsupported method\n");
return;
}
var req_sig = h.digest("base64");
if (req_sig != signature) {
r.return(401, `Invalid signature: ${req_sig}\n`);
return;
}
r.internalRedirect('@app-backend');
}
export default {authorize}
|