-
Notifications
You must be signed in to change notification settings - Fork 172
Description
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?