1

I am using the following: https://github.com/XeroAPI/xero-node

I am using a React app, talking to a Nodejs backend. The React app calls the node file connect.js as per the below:

// connect.js (node module)
const XeroClient = require('xero-node').XeroClient;
async function connect(req, res, next) {
try {
const xero = new XeroClient({
  clientId: '9.............(hidden for SO)',
  clientSecret: 'p...........(hidden for SO)',
  redirectUris: [`http://localhost:3000/xeroCallback`],
  scopes: 'openid profile email accounting.transactions offline_access'.split(" ")
});
let consentUrl = await xero.buildConsentUrl();
res.send(consentUrl);
} catch (err) {
console.log(err);
}
}
module.exports = connect;

This returns the URL to my React front end, which triggers a re-direct

This works fine, I am taking to the Xero Auth page, which then re-directs me back to localhost, where my React frontend calls on .callback.js from the back end, sending along the URL past from Xero:

{"http://localhost:3000/xeroCallback?code":"3......(hidden for SO)","scope":"openid profile email accounting.transactions","session_state":"E.........(hidden for SO)"}

Here is my code in callback.js

// callback.js (node module)
const { TokenSet } = require('openid-client');
const XeroClient = require('xero-node').XeroClient;
 async function callback(req, res) {
  const xero = new XeroClient({
  clientId: '9.............(hidden for SO)',
  clientSecret: 'p...........(hidden for SO)',
  redirectUris: [`http://localhost:3000/xeroCallback`],
  scopes: 'openid profile email accounting.transactions offline_access'.split(" ")
 });
 try {
  await xero.initialize()
  const TokenSet = await xero.apiCallback(req.body);
  res.send(TokenSet);
 } catch (err) {
   console.log(err);
 }
}
module.exports = callback;

There error is at "const TokenSet = await xero.apiCallback(req.body);" Gives me 'Access token undefined!"

Jim Dover
  • 593
  • 2
  • 12
  • 30

1 Answers1

3

So the error is because the Xero client has not yet been properly initialized.

https://github.com/XeroAPI/xero-node/blob/master/src/XeroClient.ts#L99

As you can see on the code below (linked above) the callbackParams function is a method on the openIdClient ( an oauth2.0 library ) - in order to fully setup they client you will need to call either xero.initialize() or xero.buildConsentUrl() It also looks like you should be passing back the req.url, though the req.body might still work..

this.openIdClient.callbackParams(callbackUrl)

It is setup this way to allow for more varied use cases for folks who do not need/want to access the helpers by requiring the openid-client.

// This needs to be called to setup relevant openid-client on the XeroClient

await xero.initialize()


// buildConsentUrl calls `await xero.initialize()` so if you wont 
// need to also call initialize() if you are sending user through auth

await xero.buildConsentUrl()


// You can also refresh the token without needing to initialize the openid-client
// helpful for background processes where you want to limit any dependencies (lambda, etc)

await xero.refreshWithRefreshToken(client_id, client_secret, tokenSet.refresh_token)

https://github.com/XeroAPI/xero-node

SerKnight
  • 2,502
  • 1
  • 16
  • 18
  • Ah, I see - thank you! I put in 'xero.initialize()' and it works, however now I get "access token is undefined'. I have a React app on top of a Node server, and using a Connect JS file and then a Callback JS file. Should I be combining these, to use 'xero.buildConsentUrl()' in the same file / function as the callback? – Jim Dover Jun 09 '20 at 18:09
  • Hmm well do you have a valid access_token ( part of a token_set ) that is saved from a user calback? You will need to set the access token on the client before making api calls.. have you checked out the sample node app? Its got a ton of example usage: https://github.com/XeroAPI/xero-node-oauth2-app – SerKnight Jun 09 '20 at 18:10
  • If you are coming back from an authorization flow and call `.apiCallback` - that should set the access token (+ whole token set, which includes refresh token) on the client, but if you are just setting up the client in another process once the user has already authorized you, you will need to call `await xero.setTokenSet(tokenSet)` to set the correct token set on the client. – SerKnight Jun 09 '20 at 18:12
  • Thank you, I have edited my question. Basically, my work flow is reaching to my back end to use "xero.buildConsentUrl" to get my Xero re-direct URL. Once I click Authorize in Xero, it takes me back to my app, which calls the back end module 'callback', which I use with "xero.apiCallback" - Assuming this is what generates me the Access Token? – Jim Dover Jun 09 '20 at 18:51
  • So basically, I am trying to authorize for the first time, and I assume that calling "await xero.apiCallback(url);" with the URL Xero gave me, should give me a TokenSet with an access_token populated? Otherwise i'm a little confused as to where/how I should be getting the access_token – Jim Dover Jun 09 '20 at 19:20
  • Figured it out, since I'm passing window.href from the client to the node server, this was differently encoded from what a redirect using req.url would be. Encoding the URL works and am getting my tokens now. – Jim Dover Jun 10 '20 at 01:03
  • Hey Jim - sorry didn't see your last ? - that apiCallback should be passed the returning URL from auth, which will include the `code` param which the SDK swaps out for your tokenSet - glad you got it sorted :) – SerKnight Jun 12 '20 at 18:21