-1

Trying to use node-fetch and it comes back with an empty response: {"size": 0, "timeout": 0}. This is supposed to be an OAUTH2 Access Token (basically a JWT). What did I do wrong?

async function getAccessToken() {
    if (argumentParserResult.authenticationScheme == 'OAUTH2') {
        const fetch = require('node-fetch');
        const url = argumentParserResult.resourceUrl;
        let body = {
            'grant_type': 'client_credentials',
            'client_id': argumentParserResult.clientId,
            'scope': argumentParserResult.clientScope,
            'client_secret': argumentParserResult.clientSecret
        }
        console.log('POST ' + argumentParserResult.authorizationUrl);
        console.log(JSON.stringify(body));
        let response = await fetch(argumentParserResult.authorizationUrl, {
            method: 'POST',
            body: JSON.stringify(body)
        });
        console.log('Access token request repsonse: ' + JSON.stringify(response));
        process.exit(1);
    }

}

If I hit this with a REST client, it comes back successfully using POST <some-url>:

Form-encode
grant_type: client_credentials
client_id: some-client-id
scope: some-long-scope-string
client_secret: client-secret

I'm using node-fetch 2.6.6.

halfer
  • 19,824
  • 17
  • 99
  • 186
Woodsman
  • 901
  • 21
  • 61
  • 1
    if `node-fetch` works anything like `fetch` then you've missed a step ... reading the response body, using `response.json` or `response.text` etc etc – Jaromanda X Oct 31 '22 at 04:39
  • 1
    `Form-encode` ... but you're sending JSON, not form-urlencoded? see the [documentation](https://github.com/node-fetch/node-fetch#post-with-form-parameters) for what you're doing wrong and missing – Jaromanda X Oct 31 '22 at 04:42
  • On the REST client, I send application/x-www-form-urlencoded. – Woodsman Oct 31 '22 at 13:44
  • Your `getAccessToken` function must also send a urlencoded payload ``(`grant_type=client_credentials&client_id=${argumentParserResult.clientId}&scope=${argumentParserResult.clientScope}&client_secret=${argumentParserResult.clientSecret}`)`` but it sends JSON, which the endpoint does not accept. – Heiko Theißen Oct 31 '22 at 14:14
  • @HeikoTheißen How do I fix the payload? I'm currently trying FormData() with content-type: 'application/x-www-form-urlencoded'. – Woodsman Oct 31 '22 at 14:16
  • I gave the payload in my previous comment. – Heiko Theißen Oct 31 '22 at 14:17
  • @HeikoTheißen Is the full payload urlencoded, or just the values? – Woodsman Oct 31 '22 at 20:29
  • I assume there are no special characters in client id, scope or secret, therefore no _percent_-encoding is necessary, if that's what you meant. – Heiko Theißen Oct 31 '22 at 20:35
  • Is there a synchronous version of this function? The version I'm using does not honor the await or any promises. – Woodsman Oct 31 '22 at 20:57
  • `fetch` is asynchronous and returns a promise that you can `await`. Please describe the observed behavior in more detail. – Heiko Theißen Nov 01 '22 at 09:13

1 Answers1

0

I was able to get this working by doing:

async function getAccessToken() {
    const formData = new URLSearchParams({
        'grant_type': 'client_credentials',
        'client_id': argumentParserResult.clientId,
        'scope': argumentParserResult.clientScope,
        'client_secret': argumentParserResult.clientSecret
    });

    const headers = {
        'Content-type': 'application/x-www-form-urlencoded',
    };

    return fetch(argumentParserResult.authorizationUrl, {
        method: 'POST',
        body: formData,
        headers
    }).then(res => res.json()).then(json => json);
}

let argumentParserResult = parseArguments(process.argv)
let accessToken = getAccessToken().then((accessTokenResponse) => {
    argumentParserResult.accessToken = accessTokenResponse.access_token;
    console.log('@@access-token: ' + argumentParserResult.accessToken)
    if (argumentParserResult.command == 'PUBLISH') {
        argumentParserResult.payload = readMessageFromFile(argumentParserResult.fileSpecification);
    }
    let messagingSession = new MessagingSession(getInitializedSolaceModule(), argumentParserResult);
    if (argumentParserResult.command === 'SUBSCRIBE') {
        messagingSession.log('Press Ctrl-C to exit');
        process.stdin.resume();
        //messagingSession.exit();
    }

}).catch(error => {
    console.log("Error fetching access token: " + error);
})

That said, there was and still is a need to wait for the access token to be resolved before making the connection to Solace. Not sure why people couldn't understand the need to do one step before another. This approach necessitates a somewhat messy handler chain though.

Previous solutions were on the right track, but Node never actually called the fetch service, or went in some completely different direction. That is, neither the success, nor error handlers were invoked. They may have been registered, but at no point was the asynchronous function completed enough to call a handler, and it never waited until it did.

Woodsman
  • 901
  • 21
  • 61