7

The issue is that I'm trying to retrieve a OAuth2 token. Since request has been deprecated, I'm using node-fetch for this. While I can get it to work with request, I cannot with node-fetch.

I have read numerous posts here about but none seem to actually give a consistent answer that actually works. I allow that I simply have not looked good enough. There may also be the complication that I'm wrapping this in a test, but it doesn't feel like that's the case due to the error message I'm getting.

Here is code that works (and note that I had to change details to protect internal URLs):

var request = require("request");

var options = {
  method: "POST",
  url: "https://some-url/realms/my-realm/protocol/openid-connect/token",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  form: {
    username: "JeffUser",
    password: "jeff-password",
    grant_type: "password",
    client_id: "jeff-client",
    client_secret: "jeff-client"
  }
};

request(options, function(error, response, body) {
  if (error) throw new Error(error);

  console.log(body);
})

That works and I get the token. Here is what I'm trying in node-fetch (wrapped in a test) which is failing:

const assert = require("chai").assert;
const fetch = require("node-fetch")

describe("Test API", function() {
  let api_token = "";

  it("gets a token", async function() {
    api_token = await fetch("https://some-url/realms/my-realm/protocol/openid-connect/token", {
      method: "post",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
      form: {
        username: "JeffUser",
        password: "jeff-password",
        grant_type: "password",
        client_id: "jeff-client",
        client_secret: "jeff-client"
      }
    }).then (response => {
      return response.json();
    });

    console.log(token);
  });
});

What happens here is I get the following output from that test:

{
  error: 'invalid_request',
  error_description: 'Missing form parameter: grant_type'
}

I have tried changing form to body as some searching has suggested, but that doesn't seem to have any effect. I tried wrapping the form: { } data with a JSON.stringify() as some other searching has suggested but that, too, led to the same error being reported.

Jeff Nyman
  • 870
  • 2
  • 12
  • 31
  • It _needs_ to be `body` instead of `form`. And you might need to handle encoding of the data in the proper format yourself, not sure there’s any automatism doing that for you. – CBroe Jul 02 '20 at 10:16
  • https://github.com/node-fetch/node-fetch#post-with-form-parameters - use something like `URLSearchParams` to provide your data in the proper format that `application/x-www-form-urlencoded` requires. – CBroe Jul 02 '20 at 10:18
  • @CBroe : Thank you. But then it's unclear why the `request` code works, where I don't use "body". Also there's no other logic (that I'm aware of) doing anything specially about encoding. Basically I'm trying to port what is working in `request` to `node-fetch`. I will try that `URLSearchParams` thing out. – Jeff Nyman Jul 02 '20 at 10:31
  • And that was exactly it! The `URLSearchParams` is what I needed. Thank you! – Jeff Nyman Jul 02 '20 at 10:34

3 Answers3

4

This is thanks to @CBroe who provided me the answer in comments. I'll put the solution here for others who come across this. Here is what I had to do:

const assert = require("chai").assert;
const fetch = require("node-fetch")

const params = new URLSearchParams();

params.append("grant_type", "password");
params.append("username", "JeffUser");
params.append("password", "jeff-password");
params.append("client_id", "jeff-client");
params.append("client_secret", "jeff-client");


describe("Test API", function() {
  let api_token = "";

  it("gets a token", async function() {
    api_token = await fetch("https://some-url/realms/my-realm/protocol/openid-connect/token", {
      method: "post",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
      },
      body: params
    }).then (response => {
      return response.json();
    });

    console.log(token);
  });
});

I made sure to use body instead of form. I also used the URLSearchParams to create a series of parameters, passing that into the body parameter. And that did the trick! I now get the authorization token.

Jeff Nyman
  • 870
  • 2
  • 12
  • 31
4
const buffer = require("buffer")
const fetch = require("node-fetch")

let url = 'www.exampl.com/token';
let client_id = '123';
let client_secret = '323';
let username = 'test';
let password = 'test';

let basicAuth = buffer.from(`${client_id}:${client_secret}`).toString("base64");

const response = await fetch(url, {
 method:"POST",
 headers:{ 
    "Content-Type": "application/x-www-form-urlencoded",
    Accept: "application/json; charset=UTF-8", 
     Authorization: `Basic ${basicAuth}`, 
    },
  body:`grant_type=password&username=${username}&password=${password}&scope=xfds`,
});

let token = await response.json();

console.log(token)
patrik kings
  • 442
  • 6
  • 15
1

Using async await

Put this inside the express app request and your are good to go

const authUrl = "https://accounts.spotify.com/api/token"
const bodyParams = new URLSearchParams()
bodyParams.append("grant_type",'client_credentials')

try{
    let error, response, body = await fetch(authUrl,{
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Authorization': 'Basic ' + (Buffer.from(process.env.SPTID + ':' + process.env.SPTSECRET).toString('base64'))
        },
        body: bodyParams
    })
    let response = await data.json()
    let access_token = response.access_token
    console.log(access_token)
    res.sendStatus(200)
}
catch(err){
    console.log(err)
    next(err)
}