Skip to content

Document usage for SPA's

Phil Alsford edited this page Feb 27, 2018 · 1 revision

Taken from https://github.com/XeroAPI/xero-node/issues/138. See there for discussion.

It would be good to add some documentation for developers of SPA's, because they won't be able to use this library as is on account of the oAuth flow redirecting the requests from the server, which would cause CORS issues when the requests are initiated via XHR, as you won't be able to redirect an XHR request.

I've managed to circumvent these limitations by digging into the code and figuring out the oAuth cycle implementation, but it would be good if this gets officially documented.

Anyway, for anyone else stumbling onto this problem, here's what I did to make it work:

  1. Create Xero client initalisation middleware to initialise the Xero app client with your consumer key and secret. Define an authorisation callback URL back to your server (not the SPA), see also step 6.

  2. Create a route on your server app for requestToken and have the SPA call this with an XHR request. Use the initialisation middleware prior to this step.

  3. Once initialised, use the getRequestToken method which is exposed on the Xero client: xero.getRequestToken((error, token, secret) => { ... });. Handle errors, and on a successful response, store the token and secret somewhere in a database or temporary session storage.

  4. Build the authorisation URL using the Xero client and passing in the token: const redirect = xero.buildAuthorizeUrl(token, {scope: ''});. Scope is blank for Accounting, for Payroll you need to specify scopes.

  5. Output the resulting redirect URL for the SPA (e.g. res.json({redirect});) and have the SPA redirect to this URL.

  6. Once authorised, Xero will redirect back to your server callback URL as defined in step 1. Initialise the Xero app client first as per step 1 middleware, and then obtain the following query params: const {oauth_token: token, oauth_verifier: verifier} = req.query;.

  7. Match the token against your database/session storage to ensure it's a valid request, and obtain the secret that was previously stored.

  8. Obtain an access token now using the poorly named setAccessToken method on the Xero app client, passing in all relevant params: const data = await xero.setAccessToken(token, secret, verifier);

  9. The resulting data will contain your access token/secret etc. You need to store these for making future requests to the API.

  10. To manually set the access token and secret on the Xero client app, pass these values in as configuration when initialising the app under step 1. Obtain them from storage (see step 9) and set them in the app options, e.g. along the lines of:

    //Prepare options
    const options = {
      userAgent: `${app.title}/${app.version}`,
      consumerKey,
      consumerSecret,
      authorizeCallbackUrl,
      redirectOnError: true,
    };

    //Append access token if integration present
    if (integration) {

      //Get oAuth data
      const {accessToken, accessSecret, expires} = integration.oAuth;

      //Ensure access token present and not expired
      if (accessToken && moment().isBefore(expires)) {
        Object.assign(options, {accessToken, accessSecret});
      }
    }

    //Create new Xero application instance
    const xero = new Xero.PublicApplication(options);

That will complete the cycle and you'll now be able to use the Xero SDK to make authenticated API calls. You can create some middleware to check if the integration is connected and the access token hasn't expired, and use this to show the Connect to Xero button in the SPA as needed.

Hope this helps someone, happy to provide more detailed code examples or information. For more details on these undocumented oAuth helper functions, see the code