Retrieving OAuth2 Token Via Node-Fetch
Asked Answered
X

3

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.

Xanthine answered 2/7, 2020 at 10:10 Comment(4)
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.Classicism
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.Classicism
@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.Xanthine
And that was exactly it! The URLSearchParams is what I needed. Thank you!Xanthine
X
5

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.

Xanthine answered 2/7, 2020 at 10:37 Comment(0)
D
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)
Debar answered 29/9, 2021 at 12:20 Comment(0)
P
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)
}
Pirouette answered 15/1, 2023 at 11:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.