Xero - beautiful accounting software

Xero Developer Help Center

Xero Developer Community

Community > API Authentication >

Can't refresh API token (Oauth2 PHP)

Started by Kirk Bauer -   in API Authentication

I have a back-end automation interface using the old API interface and am working on switching to Oauth2 (PHP). I used the PHP sample code to manually generate an auth token using my web browser and then I stored that information (token, Tenant ID, Expiration, and Refresh Token) in my headless scripts.

I check to see if the token has expired with each request and I am attempting to refresh it, but I receive the following error:

PHP Fatal error: Uncaught League\OAuth2\Client\Provider\Exception\IdentityProviderException: invalid_grant in /www/utils/xero2/vendor/league/oauth2-client/src/Provider/GenericProvider.php:222
Stack trace:
#0 /www/utils/xero2/vendor/league/oauth2-client/src/Provider/AbstractProvider.php(628): League\OAuth2\Client\Provider\GenericProvider->checkResponse(Object(GuzzleHttp\Psr7\Response), Array)
#1 /www/utils/xero2/vendor/league/oauth2-client/src/Provider/AbstractProvider.php(537): League\OAuth2\Client\Provider\AbstractProvider->getParsedResponse(Object(GuzzleHttp\Psr7\Request))
#2 /www/utils/xero2/lib/xero_connect.php(50): League\OAuth2\Client\Provider\AbstractProvider->getAccessToken(Object(League\OAuth2\Client\Grant\RefreshToken), Array)

The code around this is very simple, almost completely copied from the sample code, with $xero_refresh containing the Refresh Token provided to me when I received the original token.

if( $xero_expires < time() )
$provider = new \League\OAuth2\Client\Provider\GenericProvider([
'clientId' => 'xxx',
'clientSecret' => 'xxx',
'redirectUri' => 'https://.../callback.php',
'urlAuthorize' => 'https://login.xero.com/identity/connect/authorize',
'urlAccessToken' => 'https://identity.xero.com/connect/token',
'urlResourceOwnerDetails' => 'https://api.xero.com/api.xro/2.0/Organisation'

$newAccessToken = $provider->getAccessToken('refresh_token', [
'refresh_token' => $xero_refresh

The only thing I can note is that the "redirectUri" that I provide in the code above does not work when I'm doing the refresh. But I thought the refresh was supposed to be safe to do without user interaction?
Hi Kirk,

You're correct in thinking that the redirectUri isn't used for the refresh. The client_id and client_secret are used in the basic Authorization header and the body just requires the refresh_token and grant_type. Full details here.

Another thing to check is that the content-type header is application/x-www-form-urlencoded.

Adam Moore (Community Manager)