This app shows a user authentication strategy using OAuth 2.0 and OpenID Connect. Single Sign On with Xero can be a valuable tool to increase signup converion and pull/push important business data to a businesses General Ledger.
The following steps are the core pieces of code you will need to implement this in any application.
This will look something like:
https://login.xero.com/identity/connect/authorize?client_id=<CLIENT_ID>&scope=offline_access openid profile email accounting.transactions&response_type=code&redirect_uri=<CALLBACK_URI>
- offline_access: This will ensure a
refresh_token
is returned by the api so you can persist long standing API connections - openid profile email: These are Xero's supported OIDC scopes. They will return a JWT called
id_token
which you can Base64Url decode to utilize the user's information - accounting.transactions: This is a Xero specific scope that enables the interaction with an organisations accounting transactions ie. invoices & bank transactions, etc.
In the same route that matches the authorization url and the app settings in your Xero App Dashboard, you will need to catch the authorization flow temporary code and exchange for token_set
In this example we are using the xero-node SDK which has a helper to do this exchange.
const tokenSet = await xero.apiCallback(responseUrl);
The SDK also handles this under the hood with an OIDC Certified library called node-openid-client which does a sequence of cryptographic checks to ensure the token is valid and has not been tampered with.
await this.validateIdToken(tokenset, checks.nonce, 'authorization', checks.max_age, checks.state);
Once validated we can decode the JWT and access the user data within for use in our user management & login code.
const decodedIdToken = jwtDecode(tokenSet.id_token)
const userParams = {
firstName: decodedIdToken.given_name,
lastName: decodedIdToken.family_name,
email: decodedIdToken.email,
xero_userid: decodedIdToken.xero_userid,
decoded_id_token: decodedIdToken,
token_set: tokenSet,
...
}
Now that we have verified user data out of our id_token
we can lookup to see if that user already exists or not. If they do, we update any incoming data like a name change, and if not we create a new user record in our database and log them, setting a secure signed cookie variable that will persist their login session for one hour.
const user = await User.findOne({where: { email: decodedIdToken.email }})
if (user) {
await user.update(userParams).then(updatedRecord => {
console.log(`UPDATED user ${JSON.stringify(updatedRecord.email,null,2)}`)
return updatedRecord
})
} else {
await User.create(userParams).then(createdRecord => {
console.log(`CREATED user ${JSON.stringify(createdRecord.email,null,2)}`)
return createdRecord
})
}
res.cookie('recentSession', recentSession, { signed: true, maxAge: 1 * 60 * 60 * 1000 }) // 1 hour
res.redirect("dashboard");
While every web application's user management flow can vary in complexity, this code shows the basics of how to securely leverage OA2 and OIDC's access_token
and id_token
to provision accounts and leverage the power of Xero's Accounting API.
To contribute or extend to this repo get running locally through these steps:
- Install postgres
On mac I recommend using homebrew to install. For windows or Ubuntu please follow postgres' guides.
Helpful guides if you get stuck:
- MacOS Install to set that up
- Install sequelize-cli
npm install --save-dev sequelize-cli
- Create a Postgres user and database
To setup your initial PG user I reccomend reading https://medium.com/coding-blocks/creating-user-database-and-adding-access-on-postgresql-8bfcd2f4a91e
- Login to Xero Developer center https://developer.xero.com/myapps and create a new API application
- Create a
.env
file in the root of your project - Replace the variables in .env
CLIENT_ID=...
CLIENT_SECRET=...
REDIRECT_URI=...
DATABASE=...
DATABASE_USER=...
DATABASE_PASSWORD=...
PORT=5000
yarn
andnpm
are interchangeable
yarn install
yarn start
open http://localhost:5000/