The PKCE flow is required for applications like desktop and mobile apps that can’t securely store a client secret.
To get started, create an OAuth2.0 app and make sure you select the “Auth Code with PKCE” grant type. Your app will be assigned a unique Client ID but there will be no option to generate a client secret.
Single Page Apps (SPAs) are not currently supported.
Xero is a multi-tenanted platform with different types of tenants. Core Xero APIs (e.g. Accounting, Payroll, Files) operate within the context of an Organisation tenant but a tenant can also be a WorkflowMax account, a Practice Manager account or a XeroHQ practice. A user may have access to multiple tenants and will choose which ones to connect to your app. The type of tenant they can select will be determined by the scopes your app requests in the authorization step.
At the successful completion of an OAuth flow you will be granted an access token to act on behalf of the user. You will then use that access token to find out which tenants the user has connected to your app. From there, you can make API calls by providing both a valid access token and authorized tenant id with each request.
Your app should direct users to the following url:
https://login.xero.com/identity/connect/authorize?response_type=code&client_id=YOURCLIENTID&redirect_uri=YOURREDIRECTURI&scope=openid profile email accounting.transactions&state=123&code_challenge=XXXXXXXXX&code_challenge_method=S256
The following values should be passed in as parameters:
The state parameter should be used to avoid forgery attacks. Pass in a value that's unique to the user you're sending through authorisation. It will be passed back after the user completes authorisation.
The first thing your app must do before starting an authorization request is generate a “code verifier”. A code verifier is a random string between 43 and 128 characters long that consists of the characters A-Z, a-z, 0-9, and the punctuation -._~ (hyphen, period, underscore, and tilde).
The “code challenge” is created by performing a SHA256 hash on the code verifier and then Base64url encoding the hash e.g.
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
If the user authorizes your app, Xero will redirect back to your specified redirect_uri with:
If any errors occur or the user denies the request, we redirect back to your redirect_uri with an error parameter.
You can now exchange the verification code for an access token. You will also receive an identity token if you’ve requested OpenID Connect scopes and a refresh token if you’ve requested the offline_access scope.
To do this you will need to make a POST request to our token endpoint:
https://identity.xero.com/connect/token
The request body will need to contain the grant type (authorization_code), client_id, code, redirect_uri and code verifier
POST https://identity.xero.com/connect/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &client_id=zzzzzzzz &code=xxxxxx &redirect_uri=https://myapp.com/redirect &code_verifier=yyyyyyy
The token endpoint will verify all the parameters in the request, ensuring the code hasn’t expired and that the client ID matches. If everything checks out, it will generate your tokens and return them in the response.
It will contain the following parameters:
The access token is a JSON Web Token (JWT) which can be decoded to a JSON object containing information about the user and the authentication performed. One particularly useful value is the authentication_event_id which can be used in the next step to find out which tenant/s the user connected in the current auth flow.
{ "nbf": 1589363023, "exp": 1589364823, "iss": "https://identity.xero.com", "aud": "https://identity.xero.com/resources", "client_id": "91E5715B1199038080D6D0296EBC1648", "sub": "a3a4dbafh3495a808ed7a7b964388f53", "auth_time": 1589361892, "xero_userid": "1945393b-6eb7-4143-b083-7ab26cd7690b", "global_session_id": "ac2202575e824af3a181c50fcaa65c3c", "jti": "4e7747cec4ce54d6512b4b0775166c5f", "authentication_event_id": "d0ddcf81-f942-4f4d-b3c7-f98045204db4", "scope": [ "email", "profile", "openid", "accounting.transactions", "accounting.settings", "offline_access" ] }
Find out which tenants the user has authorized by calling the connections endpoint. If the user has authorized your app previously, they may have existing tenant connections. All of the connected tenants can now be accessed with the most recent access token.
If you want to retrieve the connection/s that were authorized in the current auth flow you can filter by the authEventId query parameter (e.g. ...?authEventId=d0ddcf81-f942-4f4d-b3c7-f98045204db4). You do this using the authentication_event_id found on the decoded access token.
Each connection will have a created date and an updated date. If they differ, that means the user has reconnected this tenant to your app (having previosuly connected and disconnected it).
GET https://api.xero.com/connections Authorization: "Bearer " + access_token Content-Type: application/json Response: [ { "id": "e1eede29-f875-4a5d-8470-17f6a29a88b1", "authEventId": "d99ecdfe-391d-43d2-b834-17636ba90e8d", "tenantId": "70784a63-d24b-46a9-a4db-0e70a274b056", "tenantType": "ORGANISATION", "tenantName": "Maple Florist", "createdDateUtc": "2019-07-09T23:40:30.1833130", "updatedDateUtc": "2020-05-15T01:35:13.8491980" }, { "id": "32587c85-a9b3-4306-ac30-b416e8f2c841", "authEventId": "d0ddcf81-f942-4f4d-b3c7-f98045204db4", "tenantId": "e0da6937-de07-4a14-adee-37abfac298ce", "tenantType": "ORGANISATION", "tenantName": "Adam Demo Company (NZ)", "createdDateUtc": "2020-03-23T02:24:22.2328510", "updatedDateUtc": "2020-05-13T09:43:40.7689720" }, { "id": "74305bf3-12e0-45e2-8dc8-e3ec73e3b1f9", "authEventId": "d0ddcf81-f942-4f4d-b3c7-f98045204db4", "tenantId": "c3d5e782-2153-4cda-bdb4-cec791ceb90d", "tenantType": "PRACTICEMANAGER", "tenantName": null, "createdDateUtc": "2020-01-30T01:33:36.2717380", "updatedDateUtc": "2020-02-02T19:21:08.5739590" } ]
Remember! Uncertified apps can only connect to 25 tenants. Once you've got a few customers we recommend applying to become an app partner to have this limit removed.
Make calls against the Xero APIs by simply adding the following headers to your request:
GET ../api.xro/2.0/Invoices Authorization: "Bearer " + access_token Accept: application/json Xero-tenant-id: 45e4708e-d862-4111-ab3a-dd8cd03913e1
An access tokens expire after 30 minutes. Your app can refresh an access token without user interaction by using a refresh token. You get a refresh token by requesting the offline_access scope during the initial user authorization.
Note:Your application must only request a refresh token if it is required and can be stored securely (e.g. in the keychain of the operating system).
To refresh your access token you need to POST to the token endpoint:
Remember! Uncertified apps can only connect to 25 tenants. Once you've got a few customers we recommend applying to become an app partner to have this limit removed.
https://identity.xero.com/connect/token
The request body will need to contain the grant type and refresh token
POST https://identity.xero.com/connect/token Content-Type: application/x-www-form-urlencoded grant_type=refresh_token &client_id=zzzzzzz &refresh_token=xxxxxx
Each time you perform a token refresh, you should save the new refresh token returned in the response. If, for whatever reason, your app doesn't receive the response you can retry your existing token for a grace period of 30 minutes.
If you would like to remove a tenant connection from your app (e.g. a user wants to disconnect one of their orgs) you can make a DELETE request on the Connections endpoint:
DELETE https://api.xero.com/connections/{connectionId}
You can revoke a user's refresh token and remove all their connections to your app by making a request to the revocation endpoint.
To revoke a refresh token you need to POST to the revocation endpoint:
https://identity.xero.com/connect/revocation
The request will require an authorization header containing your app’s client_id (client_secret is not required for PKCE apps).
The request body will contain the refresh token being revoked
POST https://identity.xero.com/connect/revocation authorization: "Basic " + base64encode(client_id + ":") Content-Type: application/x-www-form-urlencoded token=xxxxxx
A successful revocation request will return a 200 response with an empty body.
We have a Postman tutorial and an example .net windows forms app that demonstrate the PKCE flow in action.
Both of these can be found in the links below.