Skip to content

Getting invalid_grant after about a week #382

@sandorvasas

Description

@sandorvasas

Version 4.4.3

We deployed our pre-tested Oauth2 app to prod (pre-tested for a couple of days), and about a week later, we started getting invalid_grants when trying to refresh the token. Not sure where and why it goes wrong. For the sake of simplicity, before every xero request, an ensureAccessTokenValidity function is ran and a CRON job 2 times a week to re-new the refresh token. I'm aware refresh tokens are valid for 30 days, but to be secure and account for any CRON job outages, we try to renew it 8-9 times a month.

This is the function which is called before every Xero request:

  async ensureAccessTokenValidity(region: InstanceType<RegionSchema>) {
    const checkAndTryToRefresh = async () => {
      const xa = region.xeroAccount;
      try {
        this.Log.debug("ACCESS TOKEN SET:", JSON.stringify(xa.getXeroAccessTokenSet(), null, 4));
        const tokenSet = xa.getXeroAccessTokenSet();
        if (!tokenSet) throw new Error("Xero is not connected.");

        if (xa.getXeroAccessTokenSet().expired()) {
          this.Log.info("tokenset expired");
        }
        await xero.setTokenSet(xa.getXeroAccessTokenSet())
      
        await xero.accountingApi.getAccounts(region.xeroAccount.xeroTenantId)

        return [true, xa.getXeroAccessTokenSet()]
      }
      catch (e) {
        if (e.response && e.response.statusCode && e.response.body) {
          const {body} = e.response;
          const tokenExpiredError = body.Status == 401 && body.Detail.includes("TokenExpired");
          const authUnsuccessfulError = body.Status == 403 && body.Detail.includes("AuthenticationUnsuccessful")

          if (tokenExpiredError || authUnsuccessfulError) {
            const tokenSet = await xero.refreshToken();
            xa.setXeroAccessTokenSet(tokenSet)
            await RegionModel.updateOne({ _id: region._id }, {
              'xeroAccount.xeroAccessToken': xa.xeroAccessToken
            })
            await xero.setTokenSet(tokenSet);
            return [true, tokenSet]
          }
        } 
        return [false, e]
      }
    }

    const maxTries = 3;
    let tries = 0;
    do {
      const result = await checkAndTryToRefresh();
      if (result[0] == true) {
        return result[0];
      } else 
      if (tries == maxTries - 1) {
        throw result[1];
      } else {
        await new Promise(resolve => setTimeout(resolve, 2000));
      }
      tries++;
    } while (1)
  }

I just added the retry logic, so the invalid_grants happened without it. Now, since the invalid_grant is quite indescriptive, I'm not sure what's going on. This same function is ran by the CRON too, so basically I try to xero.accountingApi.getAccounts(region.xeroAccount.xeroTenantId) and if it fails, refresh the token.

Can it be because of Xero & our server's time mismatch? Haven't checked for that, but I read it happens with Google OAuth2. What possible reasons can there be?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions