简体   繁体   中英

How to post a subrequest with a json body to another server from NGINX http request object

Interface with Auth0 requiring a post request with a json body to retrieve the token and set it in the Header Authorization Bearer. I have trouble to set the json body in the subrequest in the nginx js mdoule for the token. Please advice

```
function introspectAccessToken(r) {
    // Prepare Authorization header for the introspection request
    var jsbody = {"grant_type" : "authorization_code",
              "client_id" : r.variables.oauth_client_id,
              "client_secret" : r.variables.oauth_client_secret,
              "code" : r.args_code,
              "redirect_uri":"http://office.etag-hk.com/login"};
    var jsString = JSON.stringify(jsbody);
    r.RequestBuffer = jsString;
    // Make the OAuth 2.0 Token Introspection request
    r.error("OAuth jsbody: " + jsString);
    r.subrequest("/_oauth2_send_introspection_request",
        function(reply) {
           if (reply.status != 200) {
               r.error("OAuth unexpected response from authorization server (HTTP " + reply.status + 
            "). " + reply.body);
                r.return(401);
            }
Thanks

I am doing something very similar and just got it 'mostly' working the way I would like just yesterday.

Disclosure: I am a NUBE to NGINX. I am posting what I did hopefully to help you, and to get any feedback on how I can make improvements.

My Use Case: I have many clients requesting data from a backend server. I need to cache the responses to reduce load on the backend, as well as authenticate to the backend server for the client. The NGINX server will manage the ID/PWD and Token, the clients will just make requests. On the very first request, or when the token expires, NGINX needs to get a fresh token. The authentication is completed with a POST request, passing the ID and Password in a JSON body.

I would like the client never to see 401. I am using the NGINX JavaScript module as you are.

I turned off caching for now to make sure I have the authentication working.

Javascript:

function introspectAccessToken(r)
{
    r.log("introspectAccessToken - make a subrequest to _oauth2_send_request");

    // Request Internal location "SEND REQUEST"
    r.subrequest("/_oauth2_send_request",
    { method: 'POST',
      body: JSON.stringify({ userName: "SERVICEID", password: "correcthorsebatterystaple" })
    },
    function(reply)
    {
       …

My body is static and for the moment, I have the ID and password in clear text in the code which is not ideal. I like how you built your body up in a variable. I think you need to put the jsString in your subrequest call rather than setting r.RequestBuffer.

In my.Conf file:

location /_oauth2_send_request {
    internal;
    proxy_method      POST;
    proxy_set_header accept "application/json";
    proxy_set_header Content-Type "application/json";
    proxy_pass https://backend.company.com/login;
}

The token is returned in the body. I parse the body and get the token data. Initially I attempted to get the token with:

js_set    $token   myJavaScript.getToken;

in the config, but r.subrequest is an asynchronous call and that is not allowed in a variable handler.
So, I 'punted' and wrote the token data to a file. This may be a BIG ERROR on my part, but it helped me to getting it to work.

Here is more of my javascript:

function(reply)
{
    if (reply.status == 200)
    {
        r.log("introspectAccessToken: POST status:" + reply.status);
        var response = JSON.parse(reply.responseBody);
        var token = response["data"];
        // Write the Token to a file -- to be read later.
        writeToken(token);
        return(200);
    else
    {
        r.log("introspectAccessToken: failed POST status:" + reply.status);
        r.return(401);
    }
});

Now that the token is in a file, I can use the variable handler to read the file ( synchronously ) and return the token data. Conf file.

js_set $myToken myJavaScript.readToken;

Where I struggled for some time was getting the initial 401 authorized redirected back to NGINX and not to the client. The directive error_page 401, in many different formats, simply did not seem to work and I even read in blogs and posts that it wouldn't work, but in others examples claiming it did work. What I think was key was the line: proxy_intercept_errors on;

After adding that, the initial 401 unauthorized error was redirected: error_page 401 = @Unauthorized;

What I have now is as follows: 1: Client makes initial GET request to get some data. The readToken function returns an empty string because token file does not exist. The response from the backend is "401 Unauthorized". 2: The 401 is redirected, and the javascript makes a subrequest to the login with the ID/PWD. 3: The response is parsed and the token is written to the file. 4: ??? The client gets a 404 Not Found code. This is what I want to improve. I would like another attempt made to the backend with the new token. 5: Failing on the first attempt, the client performs a retry. Now with a saved token on the NGINX server, the request is successful.

My full conf file:

# Location of JavaScript code
js_import ./javascript/myJavaScript.js;

js_set $myToken myJavaScript.readToken;

server
{
    listen 80;

    access_log /var/log/nginx/access.log main1;

    # This is run for every request.
    location / {
        # Make an Internal Redirect if Not Authorized.
        proxy_intercept_errors on;
        error_page 401 = @Unauthorized;

        proxy_set_header accept "application/json";
        proxy_set_header Content-Type "application/json";
        proxy_set_header Authorization $myToken;
        proxy_pass https://backend.company.com;

    }

    location  @Unauthorized {
        internal;
        auth_request /_oauth2_token_introspection;
    }

    location = /_oauth2_token_introspection {
        internal;
        # Calls the javascript function
        js_content myJavaScript.introspectAccessToken;
    }

    # Location in javascript subrequest.
    location /_oauth2_send_request {
        internal;
        proxy_method      POST;
        proxy_set_header accept "application/json";
        proxy_set_header Content-Type "application/json";
        proxy_pass https://imos-plant-config-api-test.op-epg1mi.gm.com/tonic/login;
    }
}

My full javascript:

var fs = require('fs');
var MyTokeFile = "/etc/nginx/protectedFolder/mytoken.dat";

function writeToken(t)
{
    var file = fs.writeFileSync(MyTokeFile, t)
}

function readToken(r)
{
    try {
        fs.accessSync(MyTokeFile, fs.constants.R_OK);
        r.log('readToken: Has READ access : ' + MyTokeFile);
    } catch (e) {
        r.log('readToken: No READ access : ' + MyTokeFile);
        return ("");  // Return empty string if file cannot be read.
    }

    r.log("readToken:" + MyTokeFile )
    var file = fs.readFileSync(MyTokeFile);
    var token = file.toString();
    return (token);
}

function introspectAccessToken(r)
{
    r.log("introspectAccessToken - make a subrequest to _oauth2_send_request");
    // Request Internal location "SEND REQUEST"
    r.subrequest("/_oauth2_send_request",
    { method: 'POST',
      body: JSON.stringify({ userName: "SERVICEID", password: "correcthorsebatterystaple" })
    },
    function(reply)
    {
        if (reply.status == 200)
        {
            r.log("introspectAccessToken: POST status:" + reply.status);
            var response = JSON.parse(reply.responseBody);
            var token = response["data"];
            // Write the Token to a file -- to be read later.
            writeToken(token);
            r.return(200);
        }
        else
        {
            r.log("introspectAccessToken: failed POST status:" + reply.status);
            r.return(401);     // Unexpected response, return 'auth required'
        }
    });
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM